From 230fe61b126eb740f415dd419530ac8f7cf4bc3e Mon Sep 17 00:00:00 2001 From: jmug Date: Wed, 1 Jan 2025 16:44:27 -0800 Subject: [PATCH] Add code from the book. Signed-off-by: jmug --- wcig_code_1_2/00/.envrc | 1 + wcig_code_1_2/00/src/monkey/ast/ast.go | 339 +++++ wcig_code_1_2/00/src/monkey/ast/ast_test.go | 28 + .../00/src/monkey/evaluator/builtins.go | 117 ++ .../00/src/monkey/evaluator/evaluator.go | 442 +++++++ .../00/src/monkey/evaluator/evaluator_test.go | 629 ++++++++++ wcig_code_1_2/00/src/monkey/go.mod | 1 + wcig_code_1_2/00/src/monkey/lexer/lexer.go | 157 +++ .../00/src/monkey/lexer/lexer_test.go | 143 +++ wcig_code_1_2/00/src/monkey/main.go | 19 + .../00/src/monkey/object/environment.go | 30 + wcig_code_1_2/00/src/monkey/object/object.go | 182 +++ .../00/src/monkey/object/object_test.go | 60 + wcig_code_1_2/00/src/monkey/parser/parser.go | 491 ++++++++ .../00/src/monkey/parser/parser_test.go | 1083 ++++++++++++++++ .../00/src/monkey/parser/parser_tracing.go | 32 + wcig_code_1_2/00/src/monkey/repl/repl.go | 64 + wcig_code_1_2/00/src/monkey/token/token.go | 70 ++ wcig_code_1_2/02/.envrc | 1 + wcig_code_1_2/02/src/monkey/ast/ast.go | 339 +++++ wcig_code_1_2/02/src/monkey/ast/ast_test.go | 28 + wcig_code_1_2/02/src/monkey/code/code.go | 121 ++ wcig_code_1_2/02/src/monkey/code/code_test.go | 83 ++ .../02/src/monkey/compiler/compiler.go | 92 ++ .../02/src/monkey/compiler/compiler_test.go | 135 ++ .../02/src/monkey/evaluator/builtins.go | 117 ++ .../02/src/monkey/evaluator/evaluator.go | 442 +++++++ .../02/src/monkey/evaluator/evaluator_test.go | 629 ++++++++++ wcig_code_1_2/02/src/monkey/go.mod | 3 + wcig_code_1_2/02/src/monkey/lexer/lexer.go | 157 +++ .../02/src/monkey/lexer/lexer_test.go | 143 +++ wcig_code_1_2/02/src/monkey/main.go | 19 + .../02/src/monkey/object/environment.go | 30 + wcig_code_1_2/02/src/monkey/object/object.go | 182 +++ .../02/src/monkey/object/object_test.go | 60 + wcig_code_1_2/02/src/monkey/parser/parser.go | 491 ++++++++ .../02/src/monkey/parser/parser_test.go | 1083 ++++++++++++++++ .../02/src/monkey/parser/parser_tracing.go | 32 + wcig_code_1_2/02/src/monkey/repl/repl.go | 75 ++ wcig_code_1_2/02/src/monkey/token/token.go | 70 ++ wcig_code_1_2/02/src/monkey/vm/vm.go | 81 ++ wcig_code_1_2/02/src/monkey/vm/vm_test.go | 87 ++ wcig_code_1_2/03/.envrc | 1 + wcig_code_1_2/03/src/monkey/ast/ast.go | 339 +++++ wcig_code_1_2/03/src/monkey/ast/ast_test.go | 28 + wcig_code_1_2/03/src/monkey/code/code.go | 155 +++ wcig_code_1_2/03/src/monkey/code/code_test.go | 83 ++ .../03/src/monkey/compiler/compiler.go | 140 +++ .../03/src/monkey/compiler/compiler_test.go | 277 ++++ .../03/src/monkey/evaluator/builtins.go | 117 ++ .../03/src/monkey/evaluator/evaluator.go | 442 +++++++ .../03/src/monkey/evaluator/evaluator_test.go | 629 ++++++++++ wcig_code_1_2/03/src/monkey/go.mod | 3 + wcig_code_1_2/03/src/monkey/lexer/lexer.go | 157 +++ .../03/src/monkey/lexer/lexer_test.go | 143 +++ wcig_code_1_2/03/src/monkey/main.go | 19 + .../03/src/monkey/object/environment.go | 30 + wcig_code_1_2/03/src/monkey/object/object.go | 182 +++ .../03/src/monkey/object/object_test.go | 60 + wcig_code_1_2/03/src/monkey/parser/parser.go | 491 ++++++++ .../03/src/monkey/parser/parser_test.go | 1083 ++++++++++++++++ .../03/src/monkey/parser/parser_tracing.go | 32 + wcig_code_1_2/03/src/monkey/repl/repl.go | 75 ++ wcig_code_1_2/03/src/monkey/token/token.go | 70 ++ wcig_code_1_2/03/src/monkey/vm/vm.go | 219 ++++ wcig_code_1_2/03/src/monkey/vm/vm_test.go | 154 +++ wcig_code_1_2/04/.envrc | 1 + wcig_code_1_2/04/src/monkey/ast/ast.go | 339 +++++ wcig_code_1_2/04/src/monkey/ast/ast_test.go | 28 + wcig_code_1_2/04/src/monkey/code/code.go | 165 +++ wcig_code_1_2/04/src/monkey/code/code_test.go | 83 ++ .../04/src/monkey/compiler/compiler.go | 231 ++++ .../04/src/monkey/compiler/compiler_test.go | 332 +++++ .../04/src/monkey/evaluator/builtins.go | 117 ++ .../04/src/monkey/evaluator/evaluator.go | 442 +++++++ .../04/src/monkey/evaluator/evaluator_test.go | 629 ++++++++++ wcig_code_1_2/04/src/monkey/go.mod | 3 + wcig_code_1_2/04/src/monkey/lexer/lexer.go | 157 +++ .../04/src/monkey/lexer/lexer_test.go | 143 +++ wcig_code_1_2/04/src/monkey/main.go | 19 + .../04/src/monkey/object/environment.go | 30 + wcig_code_1_2/04/src/monkey/object/object.go | 182 +++ .../04/src/monkey/object/object_test.go | 60 + wcig_code_1_2/04/src/monkey/parser/parser.go | 491 ++++++++ .../04/src/monkey/parser/parser_test.go | 1083 ++++++++++++++++ .../04/src/monkey/parser/parser_tracing.go | 32 + wcig_code_1_2/04/src/monkey/repl/repl.go | 75 ++ wcig_code_1_2/04/src/monkey/token/token.go | 70 ++ wcig_code_1_2/04/src/monkey/vm/vm.go | 256 ++++ wcig_code_1_2/04/src/monkey/vm/vm_test.go | 177 +++ wcig_code_1_2/05/.envrc | 1 + wcig_code_1_2/05/src/monkey/ast/ast.go | 339 +++++ wcig_code_1_2/05/src/monkey/ast/ast_test.go | 28 + wcig_code_1_2/05/src/monkey/code/code.go | 171 +++ wcig_code_1_2/05/src/monkey/code/code_test.go | 83 ++ .../05/src/monkey/compiler/compiler.go | 257 ++++ .../05/src/monkey/compiler/compiler_test.go | 381 ++++++ .../05/src/monkey/compiler/symbol_table.go | 35 + .../src/monkey/compiler/symbol_table_test.go | 45 + .../05/src/monkey/evaluator/builtins.go | 117 ++ .../05/src/monkey/evaluator/evaluator.go | 442 +++++++ .../05/src/monkey/evaluator/evaluator_test.go | 629 ++++++++++ wcig_code_1_2/05/src/monkey/go.mod | 3 + wcig_code_1_2/05/src/monkey/lexer/lexer.go | 157 +++ .../05/src/monkey/lexer/lexer_test.go | 143 +++ wcig_code_1_2/05/src/monkey/main.go | 19 + .../05/src/monkey/object/environment.go | 30 + wcig_code_1_2/05/src/monkey/object/object.go | 182 +++ .../05/src/monkey/object/object_test.go | 60 + wcig_code_1_2/05/src/monkey/parser/parser.go | 491 ++++++++ .../05/src/monkey/parser/parser_test.go | 1083 ++++++++++++++++ .../05/src/monkey/parser/parser_tracing.go | 32 + wcig_code_1_2/05/src/monkey/repl/repl.go | 83 ++ wcig_code_1_2/05/src/monkey/token/token.go | 70 ++ wcig_code_1_2/05/src/monkey/vm/vm.go | 281 +++++ wcig_code_1_2/05/src/monkey/vm/vm_test.go | 187 +++ wcig_code_1_2/06/.envrc | 1 + wcig_code_1_2/06/src/monkey/ast/ast.go | 339 +++++ wcig_code_1_2/06/src/monkey/ast/ast_test.go | 28 + wcig_code_1_2/06/src/monkey/code/code.go | 179 +++ wcig_code_1_2/06/src/monkey/code/code_test.go | 83 ++ .../06/src/monkey/compiler/compiler.go | 306 +++++ .../06/src/monkey/compiler/compiler_test.go | 551 ++++++++ .../06/src/monkey/compiler/symbol_table.go | 35 + .../src/monkey/compiler/symbol_table_test.go | 45 + .../06/src/monkey/evaluator/builtins.go | 117 ++ .../06/src/monkey/evaluator/evaluator.go | 442 +++++++ .../06/src/monkey/evaluator/evaluator_test.go | 629 ++++++++++ wcig_code_1_2/06/src/monkey/go.mod | 3 + wcig_code_1_2/06/src/monkey/lexer/lexer.go | 157 +++ .../06/src/monkey/lexer/lexer_test.go | 143 +++ wcig_code_1_2/06/src/monkey/main.go | 19 + .../06/src/monkey/object/environment.go | 30 + wcig_code_1_2/06/src/monkey/object/object.go | 182 +++ .../06/src/monkey/object/object_test.go | 60 + wcig_code_1_2/06/src/monkey/parser/parser.go | 491 ++++++++ .../06/src/monkey/parser/parser_test.go | 1083 ++++++++++++++++ .../06/src/monkey/parser/parser_tracing.go | 32 + wcig_code_1_2/06/src/monkey/repl/repl.go | 83 ++ wcig_code_1_2/06/src/monkey/token/token.go | 70 ++ wcig_code_1_2/06/src/monkey/vm/vm.go | 403 ++++++ wcig_code_1_2/06/src/monkey/vm/vm_test.go | 314 +++++ wcig_code_1_2/07/.envrc | 1 + wcig_code_1_2/07/src/monkey/ast/ast.go | 339 +++++ wcig_code_1_2/07/src/monkey/ast/ast_test.go | 28 + wcig_code_1_2/07/src/monkey/code/code.go | 201 +++ wcig_code_1_2/07/src/monkey/code/code_test.go | 87 ++ .../07/src/monkey/compiler/compiler.go | 428 +++++++ .../07/src/monkey/compiler/compiler_test.go | 870 +++++++++++++ .../07/src/monkey/compiler/symbol_table.go | 54 + .../src/monkey/compiler/symbol_table_test.go | 154 +++ .../07/src/monkey/evaluator/builtins.go | 117 ++ .../07/src/monkey/evaluator/evaluator.go | 442 +++++++ .../07/src/monkey/evaluator/evaluator_test.go | 629 ++++++++++ wcig_code_1_2/07/src/monkey/go.mod | 3 + wcig_code_1_2/07/src/monkey/lexer/lexer.go | 157 +++ .../07/src/monkey/lexer/lexer_test.go | 143 +++ wcig_code_1_2/07/src/monkey/main.go | 19 + .../07/src/monkey/object/environment.go | 30 + wcig_code_1_2/07/src/monkey/object/object.go | 196 +++ .../07/src/monkey/object/object_test.go | 60 + wcig_code_1_2/07/src/monkey/parser/parser.go | 491 ++++++++ .../07/src/monkey/parser/parser_test.go | 1083 ++++++++++++++++ .../07/src/monkey/parser/parser_tracing.go | 32 + wcig_code_1_2/07/src/monkey/repl/repl.go | 83 ++ wcig_code_1_2/07/src/monkey/token/token.go | 70 ++ wcig_code_1_2/07/src/monkey/vm/frame.go | 26 + wcig_code_1_2/07/src/monkey/vm/vm.go | 503 ++++++++ wcig_code_1_2/07/src/monkey/vm/vm_test.go | 573 +++++++++ wcig_code_1_2/08/.envrc | 1 + wcig_code_1_2/08/src/monkey/ast/ast.go | 339 +++++ wcig_code_1_2/08/src/monkey/ast/ast_test.go | 28 + wcig_code_1_2/08/src/monkey/code/code.go | 205 +++ wcig_code_1_2/08/src/monkey/code/code_test.go | 87 ++ .../08/src/monkey/compiler/compiler.go | 441 +++++++ .../08/src/monkey/compiler/compiler_test.go | 910 ++++++++++++++ .../08/src/monkey/compiler/symbol_table.go | 61 + .../src/monkey/compiler/symbol_table_test.go | 185 +++ .../08/src/monkey/evaluator/builtins.go | 14 + .../08/src/monkey/evaluator/evaluator.go | 445 +++++++ .../08/src/monkey/evaluator/evaluator_test.go | 629 ++++++++++ wcig_code_1_2/08/src/monkey/go.mod | 3 + wcig_code_1_2/08/src/monkey/lexer/lexer.go | 157 +++ .../08/src/monkey/lexer/lexer_test.go | 143 +++ wcig_code_1_2/08/src/monkey/main.go | 19 + .../08/src/monkey/object/builtins.go | 143 +++ .../08/src/monkey/object/environment.go | 30 + wcig_code_1_2/08/src/monkey/object/object.go | 196 +++ .../08/src/monkey/object/object_test.go | 60 + wcig_code_1_2/08/src/monkey/parser/parser.go | 491 ++++++++ .../08/src/monkey/parser/parser_test.go | 1083 ++++++++++++++++ .../08/src/monkey/parser/parser_tracing.go | 32 + wcig_code_1_2/08/src/monkey/repl/repl.go | 87 ++ wcig_code_1_2/08/src/monkey/token/token.go | 70 ++ wcig_code_1_2/08/src/monkey/vm/frame.go | 26 + wcig_code_1_2/08/src/monkey/vm/vm.go | 536 ++++++++ wcig_code_1_2/08/src/monkey/vm/vm_test.go | 630 ++++++++++ wcig_code_1_2/09/.envrc | 1 + wcig_code_1_2/09/src/monkey/ast/ast.go | 344 +++++ wcig_code_1_2/09/src/monkey/ast/ast_test.go | 28 + wcig_code_1_2/09/src/monkey/code/code.go | 219 ++++ wcig_code_1_2/09/src/monkey/code/code_test.go | 91 ++ .../09/src/monkey/compiler/compiler.go | 456 +++++++ .../09/src/monkey/compiler/compiler_test.go | 1106 ++++++++++++++++ .../09/src/monkey/compiler/symbol_table.go | 91 ++ .../src/monkey/compiler/symbol_table_test.go | 337 +++++ .../09/src/monkey/evaluator/builtins.go | 14 + .../09/src/monkey/evaluator/evaluator.go | 445 +++++++ .../09/src/monkey/evaluator/evaluator_test.go | 629 ++++++++++ wcig_code_1_2/09/src/monkey/go.mod | 3 + wcig_code_1_2/09/src/monkey/lexer/lexer.go | 157 +++ .../09/src/monkey/lexer/lexer_test.go | 143 +++ wcig_code_1_2/09/src/monkey/main.go | 19 + .../09/src/monkey/object/builtins.go | 143 +++ .../09/src/monkey/object/environment.go | 30 + wcig_code_1_2/09/src/monkey/object/object.go | 208 +++ .../09/src/monkey/object/object_test.go | 60 + wcig_code_1_2/09/src/monkey/parser/parser.go | 495 ++++++++ .../09/src/monkey/parser/parser_test.go | 1114 +++++++++++++++++ .../09/src/monkey/parser/parser_tracing.go | 32 + wcig_code_1_2/09/src/monkey/repl/repl.go | 87 ++ wcig_code_1_2/09/src/monkey/token/token.go | 70 ++ wcig_code_1_2/09/src/monkey/vm/frame.go | 26 + wcig_code_1_2/09/src/monkey/vm/vm.go | 581 +++++++++ wcig_code_1_2/09/src/monkey/vm/vm_test.go | 761 +++++++++++ wcig_code_1_2/10/.envrc | 1 + wcig_code_1_2/10/src/monkey/ast/ast.go | 344 +++++ wcig_code_1_2/10/src/monkey/ast/ast_test.go | 28 + wcig_code_1_2/10/src/monkey/benchmark/main.go | 75 ++ wcig_code_1_2/10/src/monkey/code/code.go | 219 ++++ wcig_code_1_2/10/src/monkey/code/code_test.go | 91 ++ .../10/src/monkey/compiler/compiler.go | 456 +++++++ .../10/src/monkey/compiler/compiler_test.go | 1106 ++++++++++++++++ .../10/src/monkey/compiler/symbol_table.go | 91 ++ .../src/monkey/compiler/symbol_table_test.go | 337 +++++ .../10/src/monkey/evaluator/builtins.go | 14 + .../10/src/monkey/evaluator/evaluator.go | 445 +++++++ .../10/src/monkey/evaluator/evaluator_test.go | 629 ++++++++++ wcig_code_1_2/10/src/monkey/go.mod | 3 + wcig_code_1_2/10/src/monkey/lexer/lexer.go | 157 +++ .../10/src/monkey/lexer/lexer_test.go | 143 +++ wcig_code_1_2/10/src/monkey/main.go | 19 + .../10/src/monkey/object/builtins.go | 143 +++ .../10/src/monkey/object/environment.go | 30 + wcig_code_1_2/10/src/monkey/object/object.go | 208 +++ .../10/src/monkey/object/object_test.go | 60 + wcig_code_1_2/10/src/monkey/parser/parser.go | 495 ++++++++ .../10/src/monkey/parser/parser_test.go | 1114 +++++++++++++++++ .../10/src/monkey/parser/parser_tracing.go | 32 + wcig_code_1_2/10/src/monkey/repl/repl.go | 87 ++ wcig_code_1_2/10/src/monkey/token/token.go | 70 ++ wcig_code_1_2/10/src/monkey/vm/frame.go | 26 + wcig_code_1_2/10/src/monkey/vm/vm.go | 581 +++++++++ wcig_code_1_2/10/src/monkey/vm/vm_test.go | 785 ++++++++++++ wcig_code_1_2/LICENSE | 24 + 255 files changed, 59009 insertions(+) create mode 100644 wcig_code_1_2/00/.envrc create mode 100644 wcig_code_1_2/00/src/monkey/ast/ast.go create mode 100644 wcig_code_1_2/00/src/monkey/ast/ast_test.go create mode 100644 wcig_code_1_2/00/src/monkey/evaluator/builtins.go create mode 100644 wcig_code_1_2/00/src/monkey/evaluator/evaluator.go create mode 100644 wcig_code_1_2/00/src/monkey/evaluator/evaluator_test.go create mode 100644 wcig_code_1_2/00/src/monkey/go.mod create mode 100644 wcig_code_1_2/00/src/monkey/lexer/lexer.go create mode 100644 wcig_code_1_2/00/src/monkey/lexer/lexer_test.go create mode 100644 wcig_code_1_2/00/src/monkey/main.go create mode 100644 wcig_code_1_2/00/src/monkey/object/environment.go create mode 100644 wcig_code_1_2/00/src/monkey/object/object.go create mode 100644 wcig_code_1_2/00/src/monkey/object/object_test.go create mode 100644 wcig_code_1_2/00/src/monkey/parser/parser.go create mode 100644 wcig_code_1_2/00/src/monkey/parser/parser_test.go create mode 100644 wcig_code_1_2/00/src/monkey/parser/parser_tracing.go create mode 100644 wcig_code_1_2/00/src/monkey/repl/repl.go create mode 100644 wcig_code_1_2/00/src/monkey/token/token.go create mode 100644 wcig_code_1_2/02/.envrc create mode 100644 wcig_code_1_2/02/src/monkey/ast/ast.go create mode 100644 wcig_code_1_2/02/src/monkey/ast/ast_test.go create mode 100644 wcig_code_1_2/02/src/monkey/code/code.go create mode 100644 wcig_code_1_2/02/src/monkey/code/code_test.go create mode 100644 wcig_code_1_2/02/src/monkey/compiler/compiler.go create mode 100644 wcig_code_1_2/02/src/monkey/compiler/compiler_test.go create mode 100644 wcig_code_1_2/02/src/monkey/evaluator/builtins.go create mode 100644 wcig_code_1_2/02/src/monkey/evaluator/evaluator.go create mode 100644 wcig_code_1_2/02/src/monkey/evaluator/evaluator_test.go create mode 100644 wcig_code_1_2/02/src/monkey/go.mod create mode 100644 wcig_code_1_2/02/src/monkey/lexer/lexer.go create mode 100644 wcig_code_1_2/02/src/monkey/lexer/lexer_test.go create mode 100644 wcig_code_1_2/02/src/monkey/main.go create mode 100644 wcig_code_1_2/02/src/monkey/object/environment.go create mode 100644 wcig_code_1_2/02/src/monkey/object/object.go create mode 100644 wcig_code_1_2/02/src/monkey/object/object_test.go create mode 100644 wcig_code_1_2/02/src/monkey/parser/parser.go create mode 100644 wcig_code_1_2/02/src/monkey/parser/parser_test.go create mode 100644 wcig_code_1_2/02/src/monkey/parser/parser_tracing.go create mode 100644 wcig_code_1_2/02/src/monkey/repl/repl.go create mode 100644 wcig_code_1_2/02/src/monkey/token/token.go create mode 100644 wcig_code_1_2/02/src/monkey/vm/vm.go create mode 100644 wcig_code_1_2/02/src/monkey/vm/vm_test.go create mode 100644 wcig_code_1_2/03/.envrc create mode 100644 wcig_code_1_2/03/src/monkey/ast/ast.go create mode 100644 wcig_code_1_2/03/src/monkey/ast/ast_test.go create mode 100644 wcig_code_1_2/03/src/monkey/code/code.go create mode 100644 wcig_code_1_2/03/src/monkey/code/code_test.go create mode 100644 wcig_code_1_2/03/src/monkey/compiler/compiler.go create mode 100644 wcig_code_1_2/03/src/monkey/compiler/compiler_test.go create mode 100644 wcig_code_1_2/03/src/monkey/evaluator/builtins.go create mode 100644 wcig_code_1_2/03/src/monkey/evaluator/evaluator.go create mode 100644 wcig_code_1_2/03/src/monkey/evaluator/evaluator_test.go create mode 100644 wcig_code_1_2/03/src/monkey/go.mod create mode 100644 wcig_code_1_2/03/src/monkey/lexer/lexer.go create mode 100644 wcig_code_1_2/03/src/monkey/lexer/lexer_test.go create mode 100644 wcig_code_1_2/03/src/monkey/main.go create mode 100644 wcig_code_1_2/03/src/monkey/object/environment.go create mode 100644 wcig_code_1_2/03/src/monkey/object/object.go create mode 100644 wcig_code_1_2/03/src/monkey/object/object_test.go create mode 100644 wcig_code_1_2/03/src/monkey/parser/parser.go create mode 100644 wcig_code_1_2/03/src/monkey/parser/parser_test.go create mode 100644 wcig_code_1_2/03/src/monkey/parser/parser_tracing.go create mode 100644 wcig_code_1_2/03/src/monkey/repl/repl.go create mode 100644 wcig_code_1_2/03/src/monkey/token/token.go create mode 100644 wcig_code_1_2/03/src/monkey/vm/vm.go create mode 100644 wcig_code_1_2/03/src/monkey/vm/vm_test.go create mode 100644 wcig_code_1_2/04/.envrc create mode 100644 wcig_code_1_2/04/src/monkey/ast/ast.go create mode 100644 wcig_code_1_2/04/src/monkey/ast/ast_test.go create mode 100644 wcig_code_1_2/04/src/monkey/code/code.go create mode 100644 wcig_code_1_2/04/src/monkey/code/code_test.go create mode 100644 wcig_code_1_2/04/src/monkey/compiler/compiler.go create mode 100644 wcig_code_1_2/04/src/monkey/compiler/compiler_test.go create mode 100644 wcig_code_1_2/04/src/monkey/evaluator/builtins.go create mode 100644 wcig_code_1_2/04/src/monkey/evaluator/evaluator.go create mode 100644 wcig_code_1_2/04/src/monkey/evaluator/evaluator_test.go create mode 100644 wcig_code_1_2/04/src/monkey/go.mod create mode 100644 wcig_code_1_2/04/src/monkey/lexer/lexer.go create mode 100644 wcig_code_1_2/04/src/monkey/lexer/lexer_test.go create mode 100644 wcig_code_1_2/04/src/monkey/main.go create mode 100644 wcig_code_1_2/04/src/monkey/object/environment.go create mode 100644 wcig_code_1_2/04/src/monkey/object/object.go create mode 100644 wcig_code_1_2/04/src/monkey/object/object_test.go create mode 100644 wcig_code_1_2/04/src/monkey/parser/parser.go create mode 100644 wcig_code_1_2/04/src/monkey/parser/parser_test.go create mode 100644 wcig_code_1_2/04/src/monkey/parser/parser_tracing.go create mode 100644 wcig_code_1_2/04/src/monkey/repl/repl.go create mode 100644 wcig_code_1_2/04/src/monkey/token/token.go create mode 100644 wcig_code_1_2/04/src/monkey/vm/vm.go create mode 100644 wcig_code_1_2/04/src/monkey/vm/vm_test.go create mode 100644 wcig_code_1_2/05/.envrc create mode 100644 wcig_code_1_2/05/src/monkey/ast/ast.go create mode 100644 wcig_code_1_2/05/src/monkey/ast/ast_test.go create mode 100644 wcig_code_1_2/05/src/monkey/code/code.go create mode 100644 wcig_code_1_2/05/src/monkey/code/code_test.go create mode 100644 wcig_code_1_2/05/src/monkey/compiler/compiler.go create mode 100644 wcig_code_1_2/05/src/monkey/compiler/compiler_test.go create mode 100644 wcig_code_1_2/05/src/monkey/compiler/symbol_table.go create mode 100644 wcig_code_1_2/05/src/monkey/compiler/symbol_table_test.go create mode 100644 wcig_code_1_2/05/src/monkey/evaluator/builtins.go create mode 100644 wcig_code_1_2/05/src/monkey/evaluator/evaluator.go create mode 100644 wcig_code_1_2/05/src/monkey/evaluator/evaluator_test.go create mode 100644 wcig_code_1_2/05/src/monkey/go.mod create mode 100644 wcig_code_1_2/05/src/monkey/lexer/lexer.go create mode 100644 wcig_code_1_2/05/src/monkey/lexer/lexer_test.go create mode 100644 wcig_code_1_2/05/src/monkey/main.go create mode 100644 wcig_code_1_2/05/src/monkey/object/environment.go create mode 100644 wcig_code_1_2/05/src/monkey/object/object.go create mode 100644 wcig_code_1_2/05/src/monkey/object/object_test.go create mode 100644 wcig_code_1_2/05/src/monkey/parser/parser.go create mode 100644 wcig_code_1_2/05/src/monkey/parser/parser_test.go create mode 100644 wcig_code_1_2/05/src/monkey/parser/parser_tracing.go create mode 100644 wcig_code_1_2/05/src/monkey/repl/repl.go create mode 100644 wcig_code_1_2/05/src/monkey/token/token.go create mode 100644 wcig_code_1_2/05/src/monkey/vm/vm.go create mode 100644 wcig_code_1_2/05/src/monkey/vm/vm_test.go create mode 100644 wcig_code_1_2/06/.envrc create mode 100644 wcig_code_1_2/06/src/monkey/ast/ast.go create mode 100644 wcig_code_1_2/06/src/monkey/ast/ast_test.go create mode 100644 wcig_code_1_2/06/src/monkey/code/code.go create mode 100644 wcig_code_1_2/06/src/monkey/code/code_test.go create mode 100644 wcig_code_1_2/06/src/monkey/compiler/compiler.go create mode 100644 wcig_code_1_2/06/src/monkey/compiler/compiler_test.go create mode 100644 wcig_code_1_2/06/src/monkey/compiler/symbol_table.go create mode 100644 wcig_code_1_2/06/src/monkey/compiler/symbol_table_test.go create mode 100644 wcig_code_1_2/06/src/monkey/evaluator/builtins.go create mode 100644 wcig_code_1_2/06/src/monkey/evaluator/evaluator.go create mode 100644 wcig_code_1_2/06/src/monkey/evaluator/evaluator_test.go create mode 100644 wcig_code_1_2/06/src/monkey/go.mod create mode 100644 wcig_code_1_2/06/src/monkey/lexer/lexer.go create mode 100644 wcig_code_1_2/06/src/monkey/lexer/lexer_test.go create mode 100644 wcig_code_1_2/06/src/monkey/main.go create mode 100644 wcig_code_1_2/06/src/monkey/object/environment.go create mode 100644 wcig_code_1_2/06/src/monkey/object/object.go create mode 100644 wcig_code_1_2/06/src/monkey/object/object_test.go create mode 100644 wcig_code_1_2/06/src/monkey/parser/parser.go create mode 100644 wcig_code_1_2/06/src/monkey/parser/parser_test.go create mode 100644 wcig_code_1_2/06/src/monkey/parser/parser_tracing.go create mode 100644 wcig_code_1_2/06/src/monkey/repl/repl.go create mode 100644 wcig_code_1_2/06/src/monkey/token/token.go create mode 100644 wcig_code_1_2/06/src/monkey/vm/vm.go create mode 100644 wcig_code_1_2/06/src/monkey/vm/vm_test.go create mode 100644 wcig_code_1_2/07/.envrc create mode 100644 wcig_code_1_2/07/src/monkey/ast/ast.go create mode 100644 wcig_code_1_2/07/src/monkey/ast/ast_test.go create mode 100644 wcig_code_1_2/07/src/monkey/code/code.go create mode 100644 wcig_code_1_2/07/src/monkey/code/code_test.go create mode 100644 wcig_code_1_2/07/src/monkey/compiler/compiler.go create mode 100644 wcig_code_1_2/07/src/monkey/compiler/compiler_test.go create mode 100644 wcig_code_1_2/07/src/monkey/compiler/symbol_table.go create mode 100644 wcig_code_1_2/07/src/monkey/compiler/symbol_table_test.go create mode 100644 wcig_code_1_2/07/src/monkey/evaluator/builtins.go create mode 100644 wcig_code_1_2/07/src/monkey/evaluator/evaluator.go create mode 100644 wcig_code_1_2/07/src/monkey/evaluator/evaluator_test.go create mode 100644 wcig_code_1_2/07/src/monkey/go.mod create mode 100644 wcig_code_1_2/07/src/monkey/lexer/lexer.go create mode 100644 wcig_code_1_2/07/src/monkey/lexer/lexer_test.go create mode 100644 wcig_code_1_2/07/src/monkey/main.go create mode 100644 wcig_code_1_2/07/src/monkey/object/environment.go create mode 100644 wcig_code_1_2/07/src/monkey/object/object.go create mode 100644 wcig_code_1_2/07/src/monkey/object/object_test.go create mode 100644 wcig_code_1_2/07/src/monkey/parser/parser.go create mode 100644 wcig_code_1_2/07/src/monkey/parser/parser_test.go create mode 100644 wcig_code_1_2/07/src/monkey/parser/parser_tracing.go create mode 100644 wcig_code_1_2/07/src/monkey/repl/repl.go create mode 100644 wcig_code_1_2/07/src/monkey/token/token.go create mode 100644 wcig_code_1_2/07/src/monkey/vm/frame.go create mode 100644 wcig_code_1_2/07/src/monkey/vm/vm.go create mode 100644 wcig_code_1_2/07/src/monkey/vm/vm_test.go create mode 100644 wcig_code_1_2/08/.envrc create mode 100644 wcig_code_1_2/08/src/monkey/ast/ast.go create mode 100644 wcig_code_1_2/08/src/monkey/ast/ast_test.go create mode 100644 wcig_code_1_2/08/src/monkey/code/code.go create mode 100644 wcig_code_1_2/08/src/monkey/code/code_test.go create mode 100644 wcig_code_1_2/08/src/monkey/compiler/compiler.go create mode 100644 wcig_code_1_2/08/src/monkey/compiler/compiler_test.go create mode 100644 wcig_code_1_2/08/src/monkey/compiler/symbol_table.go create mode 100644 wcig_code_1_2/08/src/monkey/compiler/symbol_table_test.go create mode 100644 wcig_code_1_2/08/src/monkey/evaluator/builtins.go create mode 100644 wcig_code_1_2/08/src/monkey/evaluator/evaluator.go create mode 100644 wcig_code_1_2/08/src/monkey/evaluator/evaluator_test.go create mode 100644 wcig_code_1_2/08/src/monkey/go.mod create mode 100644 wcig_code_1_2/08/src/monkey/lexer/lexer.go create mode 100644 wcig_code_1_2/08/src/monkey/lexer/lexer_test.go create mode 100644 wcig_code_1_2/08/src/monkey/main.go create mode 100644 wcig_code_1_2/08/src/monkey/object/builtins.go create mode 100644 wcig_code_1_2/08/src/monkey/object/environment.go create mode 100644 wcig_code_1_2/08/src/monkey/object/object.go create mode 100644 wcig_code_1_2/08/src/monkey/object/object_test.go create mode 100644 wcig_code_1_2/08/src/monkey/parser/parser.go create mode 100644 wcig_code_1_2/08/src/monkey/parser/parser_test.go create mode 100644 wcig_code_1_2/08/src/monkey/parser/parser_tracing.go create mode 100644 wcig_code_1_2/08/src/monkey/repl/repl.go create mode 100644 wcig_code_1_2/08/src/monkey/token/token.go create mode 100644 wcig_code_1_2/08/src/monkey/vm/frame.go create mode 100644 wcig_code_1_2/08/src/monkey/vm/vm.go create mode 100644 wcig_code_1_2/08/src/monkey/vm/vm_test.go create mode 100644 wcig_code_1_2/09/.envrc create mode 100644 wcig_code_1_2/09/src/monkey/ast/ast.go create mode 100644 wcig_code_1_2/09/src/monkey/ast/ast_test.go create mode 100644 wcig_code_1_2/09/src/monkey/code/code.go create mode 100644 wcig_code_1_2/09/src/monkey/code/code_test.go create mode 100644 wcig_code_1_2/09/src/monkey/compiler/compiler.go create mode 100644 wcig_code_1_2/09/src/monkey/compiler/compiler_test.go create mode 100644 wcig_code_1_2/09/src/monkey/compiler/symbol_table.go create mode 100644 wcig_code_1_2/09/src/monkey/compiler/symbol_table_test.go create mode 100644 wcig_code_1_2/09/src/monkey/evaluator/builtins.go create mode 100644 wcig_code_1_2/09/src/monkey/evaluator/evaluator.go create mode 100644 wcig_code_1_2/09/src/monkey/evaluator/evaluator_test.go create mode 100644 wcig_code_1_2/09/src/monkey/go.mod create mode 100644 wcig_code_1_2/09/src/monkey/lexer/lexer.go create mode 100644 wcig_code_1_2/09/src/monkey/lexer/lexer_test.go create mode 100644 wcig_code_1_2/09/src/monkey/main.go create mode 100644 wcig_code_1_2/09/src/monkey/object/builtins.go create mode 100644 wcig_code_1_2/09/src/monkey/object/environment.go create mode 100644 wcig_code_1_2/09/src/monkey/object/object.go create mode 100644 wcig_code_1_2/09/src/monkey/object/object_test.go create mode 100644 wcig_code_1_2/09/src/monkey/parser/parser.go create mode 100644 wcig_code_1_2/09/src/monkey/parser/parser_test.go create mode 100644 wcig_code_1_2/09/src/monkey/parser/parser_tracing.go create mode 100644 wcig_code_1_2/09/src/monkey/repl/repl.go create mode 100644 wcig_code_1_2/09/src/monkey/token/token.go create mode 100644 wcig_code_1_2/09/src/monkey/vm/frame.go create mode 100644 wcig_code_1_2/09/src/monkey/vm/vm.go create mode 100644 wcig_code_1_2/09/src/monkey/vm/vm_test.go create mode 100644 wcig_code_1_2/10/.envrc create mode 100644 wcig_code_1_2/10/src/monkey/ast/ast.go create mode 100644 wcig_code_1_2/10/src/monkey/ast/ast_test.go create mode 100644 wcig_code_1_2/10/src/monkey/benchmark/main.go create mode 100644 wcig_code_1_2/10/src/monkey/code/code.go create mode 100644 wcig_code_1_2/10/src/monkey/code/code_test.go create mode 100644 wcig_code_1_2/10/src/monkey/compiler/compiler.go create mode 100644 wcig_code_1_2/10/src/monkey/compiler/compiler_test.go create mode 100644 wcig_code_1_2/10/src/monkey/compiler/symbol_table.go create mode 100644 wcig_code_1_2/10/src/monkey/compiler/symbol_table_test.go create mode 100644 wcig_code_1_2/10/src/monkey/evaluator/builtins.go create mode 100644 wcig_code_1_2/10/src/monkey/evaluator/evaluator.go create mode 100644 wcig_code_1_2/10/src/monkey/evaluator/evaluator_test.go create mode 100644 wcig_code_1_2/10/src/monkey/go.mod create mode 100644 wcig_code_1_2/10/src/monkey/lexer/lexer.go create mode 100644 wcig_code_1_2/10/src/monkey/lexer/lexer_test.go create mode 100644 wcig_code_1_2/10/src/monkey/main.go create mode 100644 wcig_code_1_2/10/src/monkey/object/builtins.go create mode 100644 wcig_code_1_2/10/src/monkey/object/environment.go create mode 100644 wcig_code_1_2/10/src/monkey/object/object.go create mode 100644 wcig_code_1_2/10/src/monkey/object/object_test.go create mode 100644 wcig_code_1_2/10/src/monkey/parser/parser.go create mode 100644 wcig_code_1_2/10/src/monkey/parser/parser_test.go create mode 100644 wcig_code_1_2/10/src/monkey/parser/parser_tracing.go create mode 100644 wcig_code_1_2/10/src/monkey/repl/repl.go create mode 100644 wcig_code_1_2/10/src/monkey/token/token.go create mode 100644 wcig_code_1_2/10/src/monkey/vm/frame.go create mode 100644 wcig_code_1_2/10/src/monkey/vm/vm.go create mode 100644 wcig_code_1_2/10/src/monkey/vm/vm_test.go create mode 100644 wcig_code_1_2/LICENSE diff --git a/wcig_code_1_2/00/.envrc b/wcig_code_1_2/00/.envrc new file mode 100644 index 0000000..fb65ef4 --- /dev/null +++ b/wcig_code_1_2/00/.envrc @@ -0,0 +1 @@ +export GOPATH=$(pwd) diff --git a/wcig_code_1_2/00/src/monkey/ast/ast.go b/wcig_code_1_2/00/src/monkey/ast/ast.go new file mode 100644 index 0000000..fb30b05 --- /dev/null +++ b/wcig_code_1_2/00/src/monkey/ast/ast.go @@ -0,0 +1,339 @@ +package ast + +import ( + "bytes" + "monkey/token" + "strings" +) + +// The base Node interface +type Node interface { + TokenLiteral() string + String() string +} + +// All statement nodes implement this +type Statement interface { + Node + statementNode() +} + +// All expression nodes implement this +type Expression interface { + Node + expressionNode() +} + +type Program struct { + Statements []Statement +} + +func (p *Program) TokenLiteral() string { + if len(p.Statements) > 0 { + return p.Statements[0].TokenLiteral() + } else { + return "" + } +} + +func (p *Program) String() string { + var out bytes.Buffer + + for _, s := range p.Statements { + out.WriteString(s.String()) + } + + return out.String() +} + +// Statements +type LetStatement struct { + Token token.Token // the token.LET token + Name *Identifier + Value Expression +} + +func (ls *LetStatement) statementNode() {} +func (ls *LetStatement) TokenLiteral() string { return ls.Token.Literal } +func (ls *LetStatement) String() string { + var out bytes.Buffer + + out.WriteString(ls.TokenLiteral() + " ") + out.WriteString(ls.Name.String()) + out.WriteString(" = ") + + if ls.Value != nil { + out.WriteString(ls.Value.String()) + } + + out.WriteString(";") + + return out.String() +} + +type ReturnStatement struct { + Token token.Token // the 'return' token + ReturnValue Expression +} + +func (rs *ReturnStatement) statementNode() {} +func (rs *ReturnStatement) TokenLiteral() string { return rs.Token.Literal } +func (rs *ReturnStatement) String() string { + var out bytes.Buffer + + out.WriteString(rs.TokenLiteral() + " ") + + if rs.ReturnValue != nil { + out.WriteString(rs.ReturnValue.String()) + } + + out.WriteString(";") + + return out.String() +} + +type ExpressionStatement struct { + Token token.Token // the first token of the expression + Expression Expression +} + +func (es *ExpressionStatement) statementNode() {} +func (es *ExpressionStatement) TokenLiteral() string { return es.Token.Literal } +func (es *ExpressionStatement) String() string { + if es.Expression != nil { + return es.Expression.String() + } + return "" +} + +type BlockStatement struct { + Token token.Token // the { token + Statements []Statement +} + +func (bs *BlockStatement) statementNode() {} +func (bs *BlockStatement) TokenLiteral() string { return bs.Token.Literal } +func (bs *BlockStatement) String() string { + var out bytes.Buffer + + for _, s := range bs.Statements { + out.WriteString(s.String()) + } + + return out.String() +} + +// Expressions +type Identifier struct { + Token token.Token // the token.IDENT token + Value string +} + +func (i *Identifier) expressionNode() {} +func (i *Identifier) TokenLiteral() string { return i.Token.Literal } +func (i *Identifier) String() string { return i.Value } + +type Boolean struct { + Token token.Token + Value bool +} + +func (b *Boolean) expressionNode() {} +func (b *Boolean) TokenLiteral() string { return b.Token.Literal } +func (b *Boolean) String() string { return b.Token.Literal } + +type IntegerLiteral struct { + Token token.Token + Value int64 +} + +func (il *IntegerLiteral) expressionNode() {} +func (il *IntegerLiteral) TokenLiteral() string { return il.Token.Literal } +func (il *IntegerLiteral) String() string { return il.Token.Literal } + +type PrefixExpression struct { + Token token.Token // The prefix token, e.g. ! + Operator string + Right Expression +} + +func (pe *PrefixExpression) expressionNode() {} +func (pe *PrefixExpression) TokenLiteral() string { return pe.Token.Literal } +func (pe *PrefixExpression) String() string { + var out bytes.Buffer + + out.WriteString("(") + out.WriteString(pe.Operator) + out.WriteString(pe.Right.String()) + out.WriteString(")") + + return out.String() +} + +type InfixExpression struct { + Token token.Token // The operator token, e.g. + + Left Expression + Operator string + Right Expression +} + +func (oe *InfixExpression) expressionNode() {} +func (oe *InfixExpression) TokenLiteral() string { return oe.Token.Literal } +func (oe *InfixExpression) String() string { + var out bytes.Buffer + + out.WriteString("(") + out.WriteString(oe.Left.String()) + out.WriteString(" " + oe.Operator + " ") + out.WriteString(oe.Right.String()) + out.WriteString(")") + + return out.String() +} + +type IfExpression struct { + Token token.Token // The 'if' token + Condition Expression + Consequence *BlockStatement + Alternative *BlockStatement +} + +func (ie *IfExpression) expressionNode() {} +func (ie *IfExpression) TokenLiteral() string { return ie.Token.Literal } +func (ie *IfExpression) String() string { + var out bytes.Buffer + + out.WriteString("if") + out.WriteString(ie.Condition.String()) + out.WriteString(" ") + out.WriteString(ie.Consequence.String()) + + if ie.Alternative != nil { + out.WriteString("else ") + out.WriteString(ie.Alternative.String()) + } + + return out.String() +} + +type FunctionLiteral struct { + Token token.Token // The 'fn' token + Parameters []*Identifier + Body *BlockStatement +} + +func (fl *FunctionLiteral) expressionNode() {} +func (fl *FunctionLiteral) TokenLiteral() string { return fl.Token.Literal } +func (fl *FunctionLiteral) String() string { + var out bytes.Buffer + + params := []string{} + for _, p := range fl.Parameters { + params = append(params, p.String()) + } + + out.WriteString(fl.TokenLiteral()) + out.WriteString("(") + out.WriteString(strings.Join(params, ", ")) + out.WriteString(") ") + out.WriteString(fl.Body.String()) + + return out.String() +} + +type CallExpression struct { + Token token.Token // The '(' token + Function Expression // Identifier or FunctionLiteral + Arguments []Expression +} + +func (ce *CallExpression) expressionNode() {} +func (ce *CallExpression) TokenLiteral() string { return ce.Token.Literal } +func (ce *CallExpression) String() string { + var out bytes.Buffer + + args := []string{} + for _, a := range ce.Arguments { + args = append(args, a.String()) + } + + out.WriteString(ce.Function.String()) + out.WriteString("(") + out.WriteString(strings.Join(args, ", ")) + out.WriteString(")") + + return out.String() +} + +type StringLiteral struct { + Token token.Token + Value string +} + +func (sl *StringLiteral) expressionNode() {} +func (sl *StringLiteral) TokenLiteral() string { return sl.Token.Literal } +func (sl *StringLiteral) String() string { return sl.Token.Literal } + +type ArrayLiteral struct { + Token token.Token // the '[' token + Elements []Expression +} + +func (al *ArrayLiteral) expressionNode() {} +func (al *ArrayLiteral) TokenLiteral() string { return al.Token.Literal } +func (al *ArrayLiteral) String() string { + var out bytes.Buffer + + elements := []string{} + for _, el := range al.Elements { + elements = append(elements, el.String()) + } + + out.WriteString("[") + out.WriteString(strings.Join(elements, ", ")) + out.WriteString("]") + + return out.String() +} + +type IndexExpression struct { + Token token.Token // The [ token + Left Expression + Index Expression +} + +func (ie *IndexExpression) expressionNode() {} +func (ie *IndexExpression) TokenLiteral() string { return ie.Token.Literal } +func (ie *IndexExpression) String() string { + var out bytes.Buffer + + out.WriteString("(") + out.WriteString(ie.Left.String()) + out.WriteString("[") + out.WriteString(ie.Index.String()) + out.WriteString("])") + + return out.String() +} + +type HashLiteral struct { + Token token.Token // the '{' token + Pairs map[Expression]Expression +} + +func (hl *HashLiteral) expressionNode() {} +func (hl *HashLiteral) TokenLiteral() string { return hl.Token.Literal } +func (hl *HashLiteral) String() string { + var out bytes.Buffer + + pairs := []string{} + for key, value := range hl.Pairs { + pairs = append(pairs, key.String()+":"+value.String()) + } + + out.WriteString("{") + out.WriteString(strings.Join(pairs, ", ")) + out.WriteString("}") + + return out.String() +} diff --git a/wcig_code_1_2/00/src/monkey/ast/ast_test.go b/wcig_code_1_2/00/src/monkey/ast/ast_test.go new file mode 100644 index 0000000..14a49dc --- /dev/null +++ b/wcig_code_1_2/00/src/monkey/ast/ast_test.go @@ -0,0 +1,28 @@ +package ast + +import ( + "monkey/token" + "testing" +) + +func TestString(t *testing.T) { + program := &Program{ + Statements: []Statement{ + &LetStatement{ + Token: token.Token{Type: token.LET, Literal: "let"}, + Name: &Identifier{ + Token: token.Token{Type: token.IDENT, Literal: "myVar"}, + Value: "myVar", + }, + Value: &Identifier{ + Token: token.Token{Type: token.IDENT, Literal: "anotherVar"}, + Value: "anotherVar", + }, + }, + }, + } + + if program.String() != "let myVar = anotherVar;" { + t.Errorf("program.String() wrong. got=%q", program.String()) + } +} diff --git a/wcig_code_1_2/00/src/monkey/evaluator/builtins.go b/wcig_code_1_2/00/src/monkey/evaluator/builtins.go new file mode 100644 index 0000000..c4fb0f3 --- /dev/null +++ b/wcig_code_1_2/00/src/monkey/evaluator/builtins.go @@ -0,0 +1,117 @@ +package evaluator + +import ( + "fmt" + "monkey/object" +) + +var builtins = map[string]*object.Builtin{ + "len": &object.Builtin{Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", + len(args)) + } + + switch arg := args[0].(type) { + case *object.Array: + return &object.Integer{Value: int64(len(arg.Elements))} + case *object.String: + return &object.Integer{Value: int64(len(arg.Value))} + default: + return newError("argument to `len` not supported, got %s", + args[0].Type()) + } + }, + }, + "puts": &object.Builtin{ + Fn: func(args ...object.Object) object.Object { + for _, arg := range args { + fmt.Println(arg.Inspect()) + } + + return NULL + }, + }, + "first": &object.Builtin{ + Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", + len(args)) + } + if args[0].Type() != object.ARRAY_OBJ { + return newError("argument to `first` must be ARRAY, got %s", + args[0].Type()) + } + + arr := args[0].(*object.Array) + if len(arr.Elements) > 0 { + return arr.Elements[0] + } + + return NULL + }, + }, + "last": &object.Builtin{ + Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", + len(args)) + } + if args[0].Type() != object.ARRAY_OBJ { + return newError("argument to `last` must be ARRAY, got %s", + args[0].Type()) + } + + arr := args[0].(*object.Array) + length := len(arr.Elements) + if length > 0 { + return arr.Elements[length-1] + } + + return NULL + }, + }, + "rest": &object.Builtin{ + Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", + len(args)) + } + if args[0].Type() != object.ARRAY_OBJ { + return newError("argument to `rest` must be ARRAY, got %s", + args[0].Type()) + } + + arr := args[0].(*object.Array) + length := len(arr.Elements) + if length > 0 { + newElements := make([]object.Object, length-1, length-1) + copy(newElements, arr.Elements[1:length]) + return &object.Array{Elements: newElements} + } + + return NULL + }, + }, + "push": &object.Builtin{ + Fn: func(args ...object.Object) object.Object { + if len(args) != 2 { + return newError("wrong number of arguments. got=%d, want=2", + len(args)) + } + if args[0].Type() != object.ARRAY_OBJ { + return newError("argument to `push` must be ARRAY, got %s", + args[0].Type()) + } + + arr := args[0].(*object.Array) + length := len(arr.Elements) + + newElements := make([]object.Object, length+1, length+1) + copy(newElements, arr.Elements) + newElements[length] = args[1] + + return &object.Array{Elements: newElements} + }, + }, +} diff --git a/wcig_code_1_2/00/src/monkey/evaluator/evaluator.go b/wcig_code_1_2/00/src/monkey/evaluator/evaluator.go new file mode 100644 index 0000000..50b2ab5 --- /dev/null +++ b/wcig_code_1_2/00/src/monkey/evaluator/evaluator.go @@ -0,0 +1,442 @@ +package evaluator + +import ( + "fmt" + "monkey/ast" + "monkey/object" +) + +var ( + NULL = &object.Null{} + TRUE = &object.Boolean{Value: true} + FALSE = &object.Boolean{Value: false} +) + +func Eval(node ast.Node, env *object.Environment) object.Object { + switch node := node.(type) { + + // Statements + case *ast.Program: + return evalProgram(node, env) + + case *ast.BlockStatement: + return evalBlockStatement(node, env) + + case *ast.ExpressionStatement: + return Eval(node.Expression, env) + + case *ast.ReturnStatement: + val := Eval(node.ReturnValue, env) + if isError(val) { + return val + } + return &object.ReturnValue{Value: val} + + case *ast.LetStatement: + val := Eval(node.Value, env) + if isError(val) { + return val + } + env.Set(node.Name.Value, val) + + // Expressions + case *ast.IntegerLiteral: + return &object.Integer{Value: node.Value} + + case *ast.StringLiteral: + return &object.String{Value: node.Value} + + case *ast.Boolean: + return nativeBoolToBooleanObject(node.Value) + + case *ast.PrefixExpression: + right := Eval(node.Right, env) + if isError(right) { + return right + } + return evalPrefixExpression(node.Operator, right) + + case *ast.InfixExpression: + left := Eval(node.Left, env) + if isError(left) { + return left + } + + right := Eval(node.Right, env) + if isError(right) { + return right + } + + return evalInfixExpression(node.Operator, left, right) + + case *ast.IfExpression: + return evalIfExpression(node, env) + + case *ast.Identifier: + return evalIdentifier(node, env) + + case *ast.FunctionLiteral: + params := node.Parameters + body := node.Body + return &object.Function{Parameters: params, Env: env, Body: body} + + case *ast.CallExpression: + function := Eval(node.Function, env) + if isError(function) { + return function + } + + args := evalExpressions(node.Arguments, env) + if len(args) == 1 && isError(args[0]) { + return args[0] + } + + return applyFunction(function, args) + + case *ast.ArrayLiteral: + elements := evalExpressions(node.Elements, env) + if len(elements) == 1 && isError(elements[0]) { + return elements[0] + } + return &object.Array{Elements: elements} + + case *ast.IndexExpression: + left := Eval(node.Left, env) + if isError(left) { + return left + } + index := Eval(node.Index, env) + if isError(index) { + return index + } + return evalIndexExpression(left, index) + + case *ast.HashLiteral: + return evalHashLiteral(node, env) + + } + + return nil +} + +func evalProgram(program *ast.Program, env *object.Environment) object.Object { + var result object.Object + + for _, statement := range program.Statements { + result = Eval(statement, env) + + switch result := result.(type) { + case *object.ReturnValue: + return result.Value + case *object.Error: + return result + } + } + + return result +} + +func evalBlockStatement( + block *ast.BlockStatement, + env *object.Environment, +) object.Object { + var result object.Object + + for _, statement := range block.Statements { + result = Eval(statement, env) + + if result != nil { + rt := result.Type() + if rt == object.RETURN_VALUE_OBJ || rt == object.ERROR_OBJ { + return result + } + } + } + + return result +} + +func nativeBoolToBooleanObject(input bool) *object.Boolean { + if input { + return TRUE + } + return FALSE +} + +func evalPrefixExpression(operator string, right object.Object) object.Object { + switch operator { + case "!": + return evalBangOperatorExpression(right) + case "-": + return evalMinusPrefixOperatorExpression(right) + default: + return newError("unknown operator: %s%s", operator, right.Type()) + } +} + +func evalInfixExpression( + operator string, + left, right object.Object, +) object.Object { + switch { + case left.Type() == object.INTEGER_OBJ && right.Type() == object.INTEGER_OBJ: + return evalIntegerInfixExpression(operator, left, right) + case left.Type() == object.STRING_OBJ && right.Type() == object.STRING_OBJ: + return evalStringInfixExpression(operator, left, right) + case operator == "==": + return nativeBoolToBooleanObject(left == right) + case operator == "!=": + return nativeBoolToBooleanObject(left != right) + case left.Type() != right.Type(): + return newError("type mismatch: %s %s %s", + left.Type(), operator, right.Type()) + default: + return newError("unknown operator: %s %s %s", + left.Type(), operator, right.Type()) + } +} + +func evalBangOperatorExpression(right object.Object) object.Object { + switch right { + case TRUE: + return FALSE + case FALSE: + return TRUE + case NULL: + return TRUE + default: + return FALSE + } +} + +func evalMinusPrefixOperatorExpression(right object.Object) object.Object { + if right.Type() != object.INTEGER_OBJ { + return newError("unknown operator: -%s", right.Type()) + } + + value := right.(*object.Integer).Value + return &object.Integer{Value: -value} +} + +func evalIntegerInfixExpression( + operator string, + left, right object.Object, +) object.Object { + leftVal := left.(*object.Integer).Value + rightVal := right.(*object.Integer).Value + + switch operator { + case "+": + return &object.Integer{Value: leftVal + rightVal} + case "-": + return &object.Integer{Value: leftVal - rightVal} + case "*": + return &object.Integer{Value: leftVal * rightVal} + case "/": + return &object.Integer{Value: leftVal / rightVal} + case "<": + return nativeBoolToBooleanObject(leftVal < rightVal) + case ">": + return nativeBoolToBooleanObject(leftVal > rightVal) + case "==": + return nativeBoolToBooleanObject(leftVal == rightVal) + case "!=": + return nativeBoolToBooleanObject(leftVal != rightVal) + default: + return newError("unknown operator: %s %s %s", + left.Type(), operator, right.Type()) + } +} + +func evalStringInfixExpression( + operator string, + left, right object.Object, +) object.Object { + if operator != "+" { + return newError("unknown operator: %s %s %s", + left.Type(), operator, right.Type()) + } + + leftVal := left.(*object.String).Value + rightVal := right.(*object.String).Value + return &object.String{Value: leftVal + rightVal} +} + +func evalIfExpression( + ie *ast.IfExpression, + env *object.Environment, +) object.Object { + condition := Eval(ie.Condition, env) + if isError(condition) { + return condition + } + + if isTruthy(condition) { + return Eval(ie.Consequence, env) + } else if ie.Alternative != nil { + return Eval(ie.Alternative, env) + } else { + return NULL + } +} + +func evalIdentifier( + node *ast.Identifier, + env *object.Environment, +) object.Object { + if val, ok := env.Get(node.Value); ok { + return val + } + + if builtin, ok := builtins[node.Value]; ok { + return builtin + } + + return newError("identifier not found: " + node.Value) +} + +func isTruthy(obj object.Object) bool { + switch obj { + case NULL: + return false + case TRUE: + return true + case FALSE: + return false + default: + return true + } +} + +func newError(format string, a ...interface{}) *object.Error { + return &object.Error{Message: fmt.Sprintf(format, a...)} +} + +func isError(obj object.Object) bool { + if obj != nil { + return obj.Type() == object.ERROR_OBJ + } + return false +} + +func evalExpressions( + exps []ast.Expression, + env *object.Environment, +) []object.Object { + var result []object.Object + + for _, e := range exps { + evaluated := Eval(e, env) + if isError(evaluated) { + return []object.Object{evaluated} + } + result = append(result, evaluated) + } + + return result +} + +func applyFunction(fn object.Object, args []object.Object) object.Object { + switch fn := fn.(type) { + + case *object.Function: + extendedEnv := extendFunctionEnv(fn, args) + evaluated := Eval(fn.Body, extendedEnv) + return unwrapReturnValue(evaluated) + + case *object.Builtin: + return fn.Fn(args...) + + default: + return newError("not a function: %s", fn.Type()) + } +} + +func extendFunctionEnv( + fn *object.Function, + args []object.Object, +) *object.Environment { + env := object.NewEnclosedEnvironment(fn.Env) + + for paramIdx, param := range fn.Parameters { + env.Set(param.Value, args[paramIdx]) + } + + return env +} + +func unwrapReturnValue(obj object.Object) object.Object { + if returnValue, ok := obj.(*object.ReturnValue); ok { + return returnValue.Value + } + + return obj +} + +func evalIndexExpression(left, index object.Object) object.Object { + switch { + case left.Type() == object.ARRAY_OBJ && index.Type() == object.INTEGER_OBJ: + return evalArrayIndexExpression(left, index) + case left.Type() == object.HASH_OBJ: + return evalHashIndexExpression(left, index) + default: + return newError("index operator not supported: %s", left.Type()) + } +} + +func evalArrayIndexExpression(array, index object.Object) object.Object { + arrayObject := array.(*object.Array) + idx := index.(*object.Integer).Value + max := int64(len(arrayObject.Elements) - 1) + + if idx < 0 || idx > max { + return NULL + } + + return arrayObject.Elements[idx] +} + +func evalHashLiteral( + node *ast.HashLiteral, + env *object.Environment, +) object.Object { + pairs := make(map[object.HashKey]object.HashPair) + + for keyNode, valueNode := range node.Pairs { + key := Eval(keyNode, env) + if isError(key) { + return key + } + + hashKey, ok := key.(object.Hashable) + if !ok { + return newError("unusable as hash key: %s", key.Type()) + } + + value := Eval(valueNode, env) + if isError(value) { + return value + } + + hashed := hashKey.HashKey() + pairs[hashed] = object.HashPair{Key: key, Value: value} + } + + return &object.Hash{Pairs: pairs} +} + +func evalHashIndexExpression(hash, index object.Object) object.Object { + hashObject := hash.(*object.Hash) + + key, ok := index.(object.Hashable) + if !ok { + return newError("unusable as hash key: %s", index.Type()) + } + + pair, ok := hashObject.Pairs[key.HashKey()] + if !ok { + return NULL + } + + return pair.Value +} diff --git a/wcig_code_1_2/00/src/monkey/evaluator/evaluator_test.go b/wcig_code_1_2/00/src/monkey/evaluator/evaluator_test.go new file mode 100644 index 0000000..2304e6f --- /dev/null +++ b/wcig_code_1_2/00/src/monkey/evaluator/evaluator_test.go @@ -0,0 +1,629 @@ +package evaluator + +import ( + "monkey/lexer" + "monkey/object" + "monkey/parser" + "testing" +) + +func TestEvalIntegerExpression(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"5", 5}, + {"10", 10}, + {"-5", -5}, + {"-10", -10}, + {"5 + 5 + 5 + 5 - 10", 10}, + {"2 * 2 * 2 * 2 * 2", 32}, + {"-50 + 100 + -50", 0}, + {"5 * 2 + 10", 20}, + {"5 + 2 * 10", 25}, + {"20 + 2 * -10", 0}, + {"50 / 2 * 2 + 10", 60}, + {"2 * (5 + 10)", 30}, + {"3 * 3 * 3 + 10", 37}, + {"3 * (3 * 3) + 10", 37}, + {"(5 + 10 * 2 + 15 / 3) * 2 + -10", 50}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testIntegerObject(t, evaluated, tt.expected) + } +} + +func TestEvalBooleanExpression(t *testing.T) { + tests := []struct { + input string + expected bool + }{ + {"true", true}, + {"false", false}, + {"1 < 2", true}, + {"1 > 2", false}, + {"1 < 1", false}, + {"1 > 1", false}, + {"1 == 1", true}, + {"1 != 1", false}, + {"1 == 2", false}, + {"1 != 2", true}, + {"true == true", true}, + {"false == false", true}, + {"true == false", false}, + {"true != false", true}, + {"false != true", true}, + {"(1 < 2) == true", true}, + {"(1 < 2) == false", false}, + {"(1 > 2) == true", false}, + {"(1 > 2) == false", true}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testBooleanObject(t, evaluated, tt.expected) + } +} + +func TestBangOperator(t *testing.T) { + tests := []struct { + input string + expected bool + }{ + {"!true", false}, + {"!false", true}, + {"!5", false}, + {"!!true", true}, + {"!!false", false}, + {"!!5", true}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testBooleanObject(t, evaluated, tt.expected) + } +} + +func TestIfElseExpressions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + {"if (true) { 10 }", 10}, + {"if (false) { 10 }", nil}, + {"if (1) { 10 }", 10}, + {"if (1 < 2) { 10 }", 10}, + {"if (1 > 2) { 10 }", nil}, + {"if (1 > 2) { 10 } else { 20 }", 20}, + {"if (1 < 2) { 10 } else { 20 }", 10}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + integer, ok := tt.expected.(int) + if ok { + testIntegerObject(t, evaluated, int64(integer)) + } else { + testNullObject(t, evaluated) + } + } +} + +func TestReturnStatements(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"return 10;", 10}, + {"return 10; 9;", 10}, + {"return 2 * 5; 9;", 10}, + {"9; return 2 * 5; 9;", 10}, + {"if (10 > 1) { return 10; }", 10}, + { + ` +if (10 > 1) { + if (10 > 1) { + return 10; + } + + return 1; +} +`, + 10, + }, + { + ` +let f = fn(x) { + return x; + x + 10; +}; +f(10);`, + 10, + }, + { + ` +let f = fn(x) { + let result = x + 10; + return result; + return 10; +}; +f(10);`, + 20, + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testIntegerObject(t, evaluated, tt.expected) + } +} + +func TestErrorHandling(t *testing.T) { + tests := []struct { + input string + expectedMessage string + }{ + { + "5 + true;", + "type mismatch: INTEGER + BOOLEAN", + }, + { + "5 + true; 5;", + "type mismatch: INTEGER + BOOLEAN", + }, + { + "-true", + "unknown operator: -BOOLEAN", + }, + { + "true + false;", + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + "true + false + true + false;", + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + "5; true + false; 5", + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + `"Hello" - "World"`, + "unknown operator: STRING - STRING", + }, + { + "if (10 > 1) { true + false; }", + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + ` +if (10 > 1) { + if (10 > 1) { + return true + false; + } + + return 1; +} +`, + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + "foobar", + "identifier not found: foobar", + }, + { + `{"name": "Monkey"}[fn(x) { x }];`, + "unusable as hash key: FUNCTION", + }, + { + `999[1]`, + "index operator not supported: INTEGER", + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + + errObj, ok := evaluated.(*object.Error) + if !ok { + t.Errorf("no error object returned. got=%T(%+v)", + evaluated, evaluated) + continue + } + + if errObj.Message != tt.expectedMessage { + t.Errorf("wrong error message. expected=%q, got=%q", + tt.expectedMessage, errObj.Message) + } + } +} + +func TestLetStatements(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"let a = 5; a;", 5}, + {"let a = 5 * 5; a;", 25}, + {"let a = 5; let b = a; b;", 5}, + {"let a = 5; let b = a; let c = a + b + 5; c;", 15}, + } + + for _, tt := range tests { + testIntegerObject(t, testEval(tt.input), tt.expected) + } +} + +func TestFunctionObject(t *testing.T) { + input := "fn(x) { x + 2; };" + + evaluated := testEval(input) + fn, ok := evaluated.(*object.Function) + if !ok { + t.Fatalf("object is not Function. got=%T (%+v)", evaluated, evaluated) + } + + if len(fn.Parameters) != 1 { + t.Fatalf("function has wrong parameters. Parameters=%+v", + fn.Parameters) + } + + if fn.Parameters[0].String() != "x" { + t.Fatalf("parameter is not 'x'. got=%q", fn.Parameters[0]) + } + + expectedBody := "(x + 2)" + + if fn.Body.String() != expectedBody { + t.Fatalf("body is not %q. got=%q", expectedBody, fn.Body.String()) + } +} + +func TestFunctionApplication(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"let identity = fn(x) { x; }; identity(5);", 5}, + {"let identity = fn(x) { return x; }; identity(5);", 5}, + {"let double = fn(x) { x * 2; }; double(5);", 10}, + {"let add = fn(x, y) { x + y; }; add(5, 5);", 10}, + {"let add = fn(x, y) { x + y; }; add(5 + 5, add(5, 5));", 20}, + {"fn(x) { x; }(5)", 5}, + } + + for _, tt := range tests { + testIntegerObject(t, testEval(tt.input), tt.expected) + } +} + +func TestEnclosingEnvironments(t *testing.T) { + input := ` +let first = 10; +let second = 10; +let third = 10; + +let ourFunction = fn(first) { + let second = 20; + + first + second + third; +}; + +ourFunction(20) + first + second;` + + testIntegerObject(t, testEval(input), 70) +} + +func TestClosures(t *testing.T) { + input := ` +let newAdder = fn(x) { + fn(y) { x + y }; +}; + +let addTwo = newAdder(2); +addTwo(2);` + + testIntegerObject(t, testEval(input), 4) +} + +func TestStringLiteral(t *testing.T) { + input := `"Hello World!"` + + evaluated := testEval(input) + str, ok := evaluated.(*object.String) + if !ok { + t.Fatalf("object is not String. got=%T (%+v)", evaluated, evaluated) + } + + if str.Value != "Hello World!" { + t.Errorf("String has wrong value. got=%q", str.Value) + } +} + +func TestStringConcatenation(t *testing.T) { + input := `"Hello" + " " + "World!"` + + evaluated := testEval(input) + str, ok := evaluated.(*object.String) + if !ok { + t.Fatalf("object is not String. got=%T (%+v)", evaluated, evaluated) + } + + if str.Value != "Hello World!" { + t.Errorf("String has wrong value. got=%q", str.Value) + } +} + +func TestBuiltinFunctions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + {`len("")`, 0}, + {`len("four")`, 4}, + {`len("hello world")`, 11}, + {`len(1)`, "argument to `len` not supported, got INTEGER"}, + {`len("one", "two")`, "wrong number of arguments. got=2, want=1"}, + {`len([1, 2, 3])`, 3}, + {`len([])`, 0}, + {`puts("hello", "world!")`, nil}, + {`first([1, 2, 3])`, 1}, + {`first([])`, nil}, + {`first(1)`, "argument to `first` must be ARRAY, got INTEGER"}, + {`last([1, 2, 3])`, 3}, + {`last([])`, nil}, + {`last(1)`, "argument to `last` must be ARRAY, got INTEGER"}, + {`rest([1, 2, 3])`, []int{2, 3}}, + {`rest([])`, nil}, + {`push([], 1)`, []int{1}}, + {`push(1, 1)`, "argument to `push` must be ARRAY, got INTEGER"}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + + switch expected := tt.expected.(type) { + case int: + testIntegerObject(t, evaluated, int64(expected)) + case nil: + testNullObject(t, evaluated) + case string: + errObj, ok := evaluated.(*object.Error) + if !ok { + t.Errorf("object is not Error. got=%T (%+v)", + evaluated, evaluated) + continue + } + if errObj.Message != expected { + t.Errorf("wrong error message. expected=%q, got=%q", + expected, errObj.Message) + } + case []int: + array, ok := evaluated.(*object.Array) + if !ok { + t.Errorf("obj not Array. got=%T (%+v)", evaluated, evaluated) + continue + } + + if len(array.Elements) != len(expected) { + t.Errorf("wrong num of elements. want=%d, got=%d", + len(expected), len(array.Elements)) + continue + } + + for i, expectedElem := range expected { + testIntegerObject(t, array.Elements[i], int64(expectedElem)) + } + } + } +} + +func TestArrayLiterals(t *testing.T) { + input := "[1, 2 * 2, 3 + 3]" + + evaluated := testEval(input) + result, ok := evaluated.(*object.Array) + if !ok { + t.Fatalf("object is not Array. got=%T (%+v)", evaluated, evaluated) + } + + if len(result.Elements) != 3 { + t.Fatalf("array has wrong num of elements. got=%d", + len(result.Elements)) + } + + testIntegerObject(t, result.Elements[0], 1) + testIntegerObject(t, result.Elements[1], 4) + testIntegerObject(t, result.Elements[2], 6) +} + +func TestArrayIndexExpressions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + { + "[1, 2, 3][0]", + 1, + }, + { + "[1, 2, 3][1]", + 2, + }, + { + "[1, 2, 3][2]", + 3, + }, + { + "let i = 0; [1][i];", + 1, + }, + { + "[1, 2, 3][1 + 1];", + 3, + }, + { + "let myArray = [1, 2, 3]; myArray[2];", + 3, + }, + { + "let myArray = [1, 2, 3]; myArray[0] + myArray[1] + myArray[2];", + 6, + }, + { + "let myArray = [1, 2, 3]; let i = myArray[0]; myArray[i]", + 2, + }, + { + "[1, 2, 3][3]", + nil, + }, + { + "[1, 2, 3][-1]", + nil, + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + integer, ok := tt.expected.(int) + if ok { + testIntegerObject(t, evaluated, int64(integer)) + } else { + testNullObject(t, evaluated) + } + } +} + +func TestHashLiterals(t *testing.T) { + input := `let two = "two"; + { + "one": 10 - 9, + two: 1 + 1, + "thr" + "ee": 6 / 2, + 4: 4, + true: 5, + false: 6 + }` + + evaluated := testEval(input) + result, ok := evaluated.(*object.Hash) + if !ok { + t.Fatalf("Eval didn't return Hash. got=%T (%+v)", evaluated, evaluated) + } + + expected := map[object.HashKey]int64{ + (&object.String{Value: "one"}).HashKey(): 1, + (&object.String{Value: "two"}).HashKey(): 2, + (&object.String{Value: "three"}).HashKey(): 3, + (&object.Integer{Value: 4}).HashKey(): 4, + TRUE.HashKey(): 5, + FALSE.HashKey(): 6, + } + + if len(result.Pairs) != len(expected) { + t.Fatalf("Hash has wrong num of pairs. got=%d", len(result.Pairs)) + } + + for expectedKey, expectedValue := range expected { + pair, ok := result.Pairs[expectedKey] + if !ok { + t.Errorf("no pair for given key in Pairs") + } + + testIntegerObject(t, pair.Value, expectedValue) + } +} + +func TestHashIndexExpressions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + { + `{"foo": 5}["foo"]`, + 5, + }, + { + `{"foo": 5}["bar"]`, + nil, + }, + { + `let key = "foo"; {"foo": 5}[key]`, + 5, + }, + { + `{}["foo"]`, + nil, + }, + { + `{5: 5}[5]`, + 5, + }, + { + `{true: 5}[true]`, + 5, + }, + { + `{false: 5}[false]`, + 5, + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + integer, ok := tt.expected.(int) + if ok { + testIntegerObject(t, evaluated, int64(integer)) + } else { + testNullObject(t, evaluated) + } + } +} +func testEval(input string) object.Object { + l := lexer.New(input) + p := parser.New(l) + program := p.ParseProgram() + env := object.NewEnvironment() + + return Eval(program, env) +} + +func testIntegerObject(t *testing.T, obj object.Object, expected int64) bool { + result, ok := obj.(*object.Integer) + if !ok { + t.Errorf("object is not Integer. got=%T (%+v)", obj, obj) + return false + } + if result.Value != expected { + t.Errorf("object has wrong value. got=%d, want=%d", + result.Value, expected) + return false + } + + return true +} + +func testBooleanObject(t *testing.T, obj object.Object, expected bool) bool { + result, ok := obj.(*object.Boolean) + if !ok { + t.Errorf("object is not Boolean. got=%T (%+v)", obj, obj) + return false + } + if result.Value != expected { + t.Errorf("object has wrong value. got=%t, want=%t", + result.Value, expected) + return false + } + return true +} + +func testNullObject(t *testing.T, obj object.Object) bool { + if obj != NULL { + t.Errorf("object is not NULL. got=%T (%+v)", obj, obj) + return false + } + return true +} diff --git a/wcig_code_1_2/00/src/monkey/go.mod b/wcig_code_1_2/00/src/monkey/go.mod new file mode 100644 index 0000000..988bf3c --- /dev/null +++ b/wcig_code_1_2/00/src/monkey/go.mod @@ -0,0 +1 @@ +module monkey diff --git a/wcig_code_1_2/00/src/monkey/lexer/lexer.go b/wcig_code_1_2/00/src/monkey/lexer/lexer.go new file mode 100644 index 0000000..db4f5f7 --- /dev/null +++ b/wcig_code_1_2/00/src/monkey/lexer/lexer.go @@ -0,0 +1,157 @@ +package lexer + +import "monkey/token" + +type Lexer struct { + input string + position int // current position in input (points to current char) + readPosition int // current reading position in input (after current char) + ch byte // current char under examination +} + +func New(input string) *Lexer { + l := &Lexer{input: input} + l.readChar() + return l +} + +func (l *Lexer) NextToken() token.Token { + var tok token.Token + + l.skipWhitespace() + + switch l.ch { + case '=': + if l.peekChar() == '=' { + ch := l.ch + l.readChar() + literal := string(ch) + string(l.ch) + tok = token.Token{Type: token.EQ, Literal: literal} + } else { + tok = newToken(token.ASSIGN, l.ch) + } + case '+': + tok = newToken(token.PLUS, l.ch) + case '-': + tok = newToken(token.MINUS, l.ch) + case '!': + if l.peekChar() == '=' { + ch := l.ch + l.readChar() + literal := string(ch) + string(l.ch) + tok = token.Token{Type: token.NOT_EQ, Literal: literal} + } else { + tok = newToken(token.BANG, l.ch) + } + case '/': + tok = newToken(token.SLASH, l.ch) + case '*': + tok = newToken(token.ASTERISK, l.ch) + case '<': + tok = newToken(token.LT, l.ch) + case '>': + tok = newToken(token.GT, l.ch) + case ';': + tok = newToken(token.SEMICOLON, l.ch) + case ':': + tok = newToken(token.COLON, l.ch) + case ',': + tok = newToken(token.COMMA, l.ch) + case '{': + tok = newToken(token.LBRACE, l.ch) + case '}': + tok = newToken(token.RBRACE, l.ch) + case '(': + tok = newToken(token.LPAREN, l.ch) + case ')': + tok = newToken(token.RPAREN, l.ch) + case '"': + tok.Type = token.STRING + tok.Literal = l.readString() + case '[': + tok = newToken(token.LBRACKET, l.ch) + case ']': + tok = newToken(token.RBRACKET, l.ch) + case 0: + tok.Literal = "" + tok.Type = token.EOF + default: + if isLetter(l.ch) { + tok.Literal = l.readIdentifier() + tok.Type = token.LookupIdent(tok.Literal) + return tok + } else if isDigit(l.ch) { + tok.Type = token.INT + tok.Literal = l.readNumber() + return tok + } else { + tok = newToken(token.ILLEGAL, l.ch) + } + } + + l.readChar() + return tok +} + +func (l *Lexer) skipWhitespace() { + for l.ch == ' ' || l.ch == '\t' || l.ch == '\n' || l.ch == '\r' { + l.readChar() + } +} + +func (l *Lexer) readChar() { + if l.readPosition >= len(l.input) { + l.ch = 0 + } else { + l.ch = l.input[l.readPosition] + } + l.position = l.readPosition + l.readPosition += 1 +} + +func (l *Lexer) peekChar() byte { + if l.readPosition >= len(l.input) { + return 0 + } else { + return l.input[l.readPosition] + } +} + +func (l *Lexer) readIdentifier() string { + position := l.position + for isLetter(l.ch) { + l.readChar() + } + return l.input[position:l.position] +} + +func (l *Lexer) readNumber() string { + position := l.position + for isDigit(l.ch) { + l.readChar() + } + return l.input[position:l.position] +} + +func (l *Lexer) readString() string { + position := l.position + 1 + for { + l.readChar() + if l.ch == '"' || l.ch == 0 { + break + } + } + return l.input[position:l.position] +} + +func isLetter(ch byte) bool { + return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' +} + +func isDigit(ch byte) bool { + return '0' <= ch && ch <= '9' +} + +func newToken(tokenType token.TokenType, ch byte) token.Token { + return token.Token{Type: tokenType, Literal: string(ch)} +} diff --git a/wcig_code_1_2/00/src/monkey/lexer/lexer_test.go b/wcig_code_1_2/00/src/monkey/lexer/lexer_test.go new file mode 100644 index 0000000..e4e64a4 --- /dev/null +++ b/wcig_code_1_2/00/src/monkey/lexer/lexer_test.go @@ -0,0 +1,143 @@ +package lexer + +import ( + "testing" + + "monkey/token" +) + +func TestNextToken(t *testing.T) { + input := `let five = 5; +let ten = 10; + +let add = fn(x, y) { + x + y; +}; + +let result = add(five, ten); +!-/*5; +5 < 10 > 5; + +if (5 < 10) { + return true; +} else { + return false; +} + +10 == 10; +10 != 9; +"foobar" +"foo bar" +[1, 2]; +{"foo": "bar"} +` + + tests := []struct { + expectedType token.TokenType + expectedLiteral string + }{ + {token.LET, "let"}, + {token.IDENT, "five"}, + {token.ASSIGN, "="}, + {token.INT, "5"}, + {token.SEMICOLON, ";"}, + {token.LET, "let"}, + {token.IDENT, "ten"}, + {token.ASSIGN, "="}, + {token.INT, "10"}, + {token.SEMICOLON, ";"}, + {token.LET, "let"}, + {token.IDENT, "add"}, + {token.ASSIGN, "="}, + {token.FUNCTION, "fn"}, + {token.LPAREN, "("}, + {token.IDENT, "x"}, + {token.COMMA, ","}, + {token.IDENT, "y"}, + {token.RPAREN, ")"}, + {token.LBRACE, "{"}, + {token.IDENT, "x"}, + {token.PLUS, "+"}, + {token.IDENT, "y"}, + {token.SEMICOLON, ";"}, + {token.RBRACE, "}"}, + {token.SEMICOLON, ";"}, + {token.LET, "let"}, + {token.IDENT, "result"}, + {token.ASSIGN, "="}, + {token.IDENT, "add"}, + {token.LPAREN, "("}, + {token.IDENT, "five"}, + {token.COMMA, ","}, + {token.IDENT, "ten"}, + {token.RPAREN, ")"}, + {token.SEMICOLON, ";"}, + {token.BANG, "!"}, + {token.MINUS, "-"}, + {token.SLASH, "/"}, + {token.ASTERISK, "*"}, + {token.INT, "5"}, + {token.SEMICOLON, ";"}, + {token.INT, "5"}, + {token.LT, "<"}, + {token.INT, "10"}, + {token.GT, ">"}, + {token.INT, "5"}, + {token.SEMICOLON, ";"}, + {token.IF, "if"}, + {token.LPAREN, "("}, + {token.INT, "5"}, + {token.LT, "<"}, + {token.INT, "10"}, + {token.RPAREN, ")"}, + {token.LBRACE, "{"}, + {token.RETURN, "return"}, + {token.TRUE, "true"}, + {token.SEMICOLON, ";"}, + {token.RBRACE, "}"}, + {token.ELSE, "else"}, + {token.LBRACE, "{"}, + {token.RETURN, "return"}, + {token.FALSE, "false"}, + {token.SEMICOLON, ";"}, + {token.RBRACE, "}"}, + {token.INT, "10"}, + {token.EQ, "=="}, + {token.INT, "10"}, + {token.SEMICOLON, ";"}, + {token.INT, "10"}, + {token.NOT_EQ, "!="}, + {token.INT, "9"}, + {token.SEMICOLON, ";"}, + {token.STRING, "foobar"}, + {token.STRING, "foo bar"}, + {token.LBRACKET, "["}, + {token.INT, "1"}, + {token.COMMA, ","}, + {token.INT, "2"}, + {token.RBRACKET, "]"}, + {token.SEMICOLON, ";"}, + {token.LBRACE, "{"}, + {token.STRING, "foo"}, + {token.COLON, ":"}, + {token.STRING, "bar"}, + {token.RBRACE, "}"}, + {token.EOF, ""}, + } + + l := New(input) + + for i, tt := range tests { + tok := l.NextToken() + + if tok.Type != tt.expectedType { + t.Fatalf("tests[%d] - tokentype wrong. expected=%q, got=%q", + i, tt.expectedType, tok.Type) + } + + if tok.Literal != tt.expectedLiteral { + t.Fatalf("tests[%d] - literal wrong. expected=%q, got=%q", + i, tt.expectedLiteral, tok.Literal) + } + } +} diff --git a/wcig_code_1_2/00/src/monkey/main.go b/wcig_code_1_2/00/src/monkey/main.go new file mode 100644 index 0000000..1d27138 --- /dev/null +++ b/wcig_code_1_2/00/src/monkey/main.go @@ -0,0 +1,19 @@ +package main + +import ( + "fmt" + "monkey/repl" + "os" + "os/user" +) + +func main() { + user, err := user.Current() + if err != nil { + panic(err) + } + fmt.Printf("Hello %s! This is the Monkey programming language!\n", + user.Username) + fmt.Printf("Feel free to type in commands\n") + repl.Start(os.Stdin, os.Stdout) +} diff --git a/wcig_code_1_2/00/src/monkey/object/environment.go b/wcig_code_1_2/00/src/monkey/object/environment.go new file mode 100644 index 0000000..6f31070 --- /dev/null +++ b/wcig_code_1_2/00/src/monkey/object/environment.go @@ -0,0 +1,30 @@ +package object + +func NewEnclosedEnvironment(outer *Environment) *Environment { + env := NewEnvironment() + env.outer = outer + return env +} + +func NewEnvironment() *Environment { + s := make(map[string]Object) + return &Environment{store: s, outer: nil} +} + +type Environment struct { + store map[string]Object + outer *Environment +} + +func (e *Environment) Get(name string) (Object, bool) { + obj, ok := e.store[name] + if !ok && e.outer != nil { + obj, ok = e.outer.Get(name) + } + return obj, ok +} + +func (e *Environment) Set(name string, val Object) Object { + e.store[name] = val + return val +} diff --git a/wcig_code_1_2/00/src/monkey/object/object.go b/wcig_code_1_2/00/src/monkey/object/object.go new file mode 100644 index 0000000..2c2a1b0 --- /dev/null +++ b/wcig_code_1_2/00/src/monkey/object/object.go @@ -0,0 +1,182 @@ +package object + +import ( + "bytes" + "fmt" + "hash/fnv" + "monkey/ast" + "strings" +) + +type BuiltinFunction func(args ...Object) Object + +type ObjectType string + +const ( + NULL_OBJ = "NULL" + ERROR_OBJ = "ERROR" + + INTEGER_OBJ = "INTEGER" + BOOLEAN_OBJ = "BOOLEAN" + STRING_OBJ = "STRING" + + RETURN_VALUE_OBJ = "RETURN_VALUE" + + FUNCTION_OBJ = "FUNCTION" + BUILTIN_OBJ = "BUILTIN" + + ARRAY_OBJ = "ARRAY" + HASH_OBJ = "HASH" +) + +type HashKey struct { + Type ObjectType + Value uint64 +} + +type Hashable interface { + HashKey() HashKey +} + +type Object interface { + Type() ObjectType + Inspect() string +} + +type Integer struct { + Value int64 +} + +func (i *Integer) Type() ObjectType { return INTEGER_OBJ } +func (i *Integer) Inspect() string { return fmt.Sprintf("%d", i.Value) } +func (i *Integer) HashKey() HashKey { + return HashKey{Type: i.Type(), Value: uint64(i.Value)} +} + +type Boolean struct { + Value bool +} + +func (b *Boolean) Type() ObjectType { return BOOLEAN_OBJ } +func (b *Boolean) Inspect() string { return fmt.Sprintf("%t", b.Value) } +func (b *Boolean) HashKey() HashKey { + var value uint64 + + if b.Value { + value = 1 + } else { + value = 0 + } + + return HashKey{Type: b.Type(), Value: value} +} + +type Null struct{} + +func (n *Null) Type() ObjectType { return NULL_OBJ } +func (n *Null) Inspect() string { return "null" } + +type ReturnValue struct { + Value Object +} + +func (rv *ReturnValue) Type() ObjectType { return RETURN_VALUE_OBJ } +func (rv *ReturnValue) Inspect() string { return rv.Value.Inspect() } + +type Error struct { + Message string +} + +func (e *Error) Type() ObjectType { return ERROR_OBJ } +func (e *Error) Inspect() string { return "ERROR: " + e.Message } + +type Function struct { + Parameters []*ast.Identifier + Body *ast.BlockStatement + Env *Environment +} + +func (f *Function) Type() ObjectType { return FUNCTION_OBJ } +func (f *Function) Inspect() string { + var out bytes.Buffer + + params := []string{} + for _, p := range f.Parameters { + params = append(params, p.String()) + } + + out.WriteString("fn") + out.WriteString("(") + out.WriteString(strings.Join(params, ", ")) + out.WriteString(") {\n") + out.WriteString(f.Body.String()) + out.WriteString("\n}") + + return out.String() +} + +type String struct { + Value string +} + +func (s *String) Type() ObjectType { return STRING_OBJ } +func (s *String) Inspect() string { return s.Value } +func (s *String) HashKey() HashKey { + h := fnv.New64a() + h.Write([]byte(s.Value)) + + return HashKey{Type: s.Type(), Value: h.Sum64()} +} + +type Builtin struct { + Fn BuiltinFunction +} + +func (b *Builtin) Type() ObjectType { return BUILTIN_OBJ } +func (b *Builtin) Inspect() string { return "builtin function" } + +type Array struct { + Elements []Object +} + +func (ao *Array) Type() ObjectType { return ARRAY_OBJ } +func (ao *Array) Inspect() string { + var out bytes.Buffer + + elements := []string{} + for _, e := range ao.Elements { + elements = append(elements, e.Inspect()) + } + + out.WriteString("[") + out.WriteString(strings.Join(elements, ", ")) + out.WriteString("]") + + return out.String() +} + +type HashPair struct { + Key Object + Value Object +} + +type Hash struct { + Pairs map[HashKey]HashPair +} + +func (h *Hash) Type() ObjectType { return HASH_OBJ } +func (h *Hash) Inspect() string { + var out bytes.Buffer + + pairs := []string{} + for _, pair := range h.Pairs { + pairs = append(pairs, fmt.Sprintf("%s: %s", + pair.Key.Inspect(), pair.Value.Inspect())) + } + + out.WriteString("{") + out.WriteString(strings.Join(pairs, ", ")) + out.WriteString("}") + + return out.String() +} diff --git a/wcig_code_1_2/00/src/monkey/object/object_test.go b/wcig_code_1_2/00/src/monkey/object/object_test.go new file mode 100644 index 0000000..63228a2 --- /dev/null +++ b/wcig_code_1_2/00/src/monkey/object/object_test.go @@ -0,0 +1,60 @@ +package object + +import "testing" + +func TestStringHashKey(t *testing.T) { + hello1 := &String{Value: "Hello World"} + hello2 := &String{Value: "Hello World"} + diff1 := &String{Value: "My name is johnny"} + diff2 := &String{Value: "My name is johnny"} + + if hello1.HashKey() != hello2.HashKey() { + t.Errorf("strings with same content have different hash keys") + } + + if diff1.HashKey() != diff2.HashKey() { + t.Errorf("strings with same content have different hash keys") + } + + if hello1.HashKey() == diff1.HashKey() { + t.Errorf("strings with different content have same hash keys") + } +} + +func TestBooleanHashKey(t *testing.T) { + true1 := &Boolean{Value: true} + true2 := &Boolean{Value: true} + false1 := &Boolean{Value: false} + false2 := &Boolean{Value: false} + + if true1.HashKey() != true2.HashKey() { + t.Errorf("trues do not have same hash key") + } + + if false1.HashKey() != false2.HashKey() { + t.Errorf("falses do not have same hash key") + } + + if true1.HashKey() == false1.HashKey() { + t.Errorf("true has same hash key as false") + } +} + +func TestIntegerHashKey(t *testing.T) { + one1 := &Integer{Value: 1} + one2 := &Integer{Value: 1} + two1 := &Integer{Value: 2} + two2 := &Integer{Value: 2} + + if one1.HashKey() != one2.HashKey() { + t.Errorf("integers with same content have twoerent hash keys") + } + + if two1.HashKey() != two2.HashKey() { + t.Errorf("integers with same content have twoerent hash keys") + } + + if one1.HashKey() == two1.HashKey() { + t.Errorf("integers with twoerent content have same hash keys") + } +} diff --git a/wcig_code_1_2/00/src/monkey/parser/parser.go b/wcig_code_1_2/00/src/monkey/parser/parser.go new file mode 100644 index 0000000..0a1f19c --- /dev/null +++ b/wcig_code_1_2/00/src/monkey/parser/parser.go @@ -0,0 +1,491 @@ +package parser + +import ( + "fmt" + "monkey/ast" + "monkey/lexer" + "monkey/token" + "strconv" +) + +const ( + _ int = iota + LOWEST + EQUALS // == + LESSGREATER // > or < + SUM // + + PRODUCT // * + PREFIX // -X or !X + CALL // myFunction(X) + INDEX // array[index] +) + +var precedences = map[token.TokenType]int{ + token.EQ: EQUALS, + token.NOT_EQ: EQUALS, + token.LT: LESSGREATER, + token.GT: LESSGREATER, + token.PLUS: SUM, + token.MINUS: SUM, + token.SLASH: PRODUCT, + token.ASTERISK: PRODUCT, + token.LPAREN: CALL, + token.LBRACKET: INDEX, +} + +type ( + prefixParseFn func() ast.Expression + infixParseFn func(ast.Expression) ast.Expression +) + +type Parser struct { + l *lexer.Lexer + errors []string + + curToken token.Token + peekToken token.Token + + prefixParseFns map[token.TokenType]prefixParseFn + infixParseFns map[token.TokenType]infixParseFn +} + +func New(l *lexer.Lexer) *Parser { + p := &Parser{ + l: l, + errors: []string{}, + } + + p.prefixParseFns = make(map[token.TokenType]prefixParseFn) + p.registerPrefix(token.IDENT, p.parseIdentifier) + p.registerPrefix(token.INT, p.parseIntegerLiteral) + p.registerPrefix(token.STRING, p.parseStringLiteral) + p.registerPrefix(token.BANG, p.parsePrefixExpression) + p.registerPrefix(token.MINUS, p.parsePrefixExpression) + p.registerPrefix(token.TRUE, p.parseBoolean) + p.registerPrefix(token.FALSE, p.parseBoolean) + p.registerPrefix(token.LPAREN, p.parseGroupedExpression) + p.registerPrefix(token.IF, p.parseIfExpression) + p.registerPrefix(token.FUNCTION, p.parseFunctionLiteral) + p.registerPrefix(token.LBRACKET, p.parseArrayLiteral) + p.registerPrefix(token.LBRACE, p.parseHashLiteral) + + p.infixParseFns = make(map[token.TokenType]infixParseFn) + p.registerInfix(token.PLUS, p.parseInfixExpression) + p.registerInfix(token.MINUS, p.parseInfixExpression) + p.registerInfix(token.SLASH, p.parseInfixExpression) + p.registerInfix(token.ASTERISK, p.parseInfixExpression) + p.registerInfix(token.EQ, p.parseInfixExpression) + p.registerInfix(token.NOT_EQ, p.parseInfixExpression) + p.registerInfix(token.LT, p.parseInfixExpression) + p.registerInfix(token.GT, p.parseInfixExpression) + + p.registerInfix(token.LPAREN, p.parseCallExpression) + p.registerInfix(token.LBRACKET, p.parseIndexExpression) + + // Read two tokens, so curToken and peekToken are both set + p.nextToken() + p.nextToken() + + return p +} + +func (p *Parser) nextToken() { + p.curToken = p.peekToken + p.peekToken = p.l.NextToken() +} + +func (p *Parser) curTokenIs(t token.TokenType) bool { + return p.curToken.Type == t +} + +func (p *Parser) peekTokenIs(t token.TokenType) bool { + return p.peekToken.Type == t +} + +func (p *Parser) expectPeek(t token.TokenType) bool { + if p.peekTokenIs(t) { + p.nextToken() + return true + } else { + p.peekError(t) + return false + } +} + +func (p *Parser) Errors() []string { + return p.errors +} + +func (p *Parser) peekError(t token.TokenType) { + msg := fmt.Sprintf("expected next token to be %s, got %s instead", + t, p.peekToken.Type) + p.errors = append(p.errors, msg) +} + +func (p *Parser) noPrefixParseFnError(t token.TokenType) { + msg := fmt.Sprintf("no prefix parse function for %s found", t) + p.errors = append(p.errors, msg) +} + +func (p *Parser) ParseProgram() *ast.Program { + program := &ast.Program{} + program.Statements = []ast.Statement{} + + for !p.curTokenIs(token.EOF) { + stmt := p.parseStatement() + if stmt != nil { + program.Statements = append(program.Statements, stmt) + } + p.nextToken() + } + + return program +} + +func (p *Parser) parseStatement() ast.Statement { + switch p.curToken.Type { + case token.LET: + return p.parseLetStatement() + case token.RETURN: + return p.parseReturnStatement() + default: + return p.parseExpressionStatement() + } +} + +func (p *Parser) parseLetStatement() *ast.LetStatement { + stmt := &ast.LetStatement{Token: p.curToken} + + if !p.expectPeek(token.IDENT) { + return nil + } + + stmt.Name = &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + + if !p.expectPeek(token.ASSIGN) { + return nil + } + + p.nextToken() + + stmt.Value = p.parseExpression(LOWEST) + + if p.peekTokenIs(token.SEMICOLON) { + p.nextToken() + } + + return stmt +} + +func (p *Parser) parseReturnStatement() *ast.ReturnStatement { + stmt := &ast.ReturnStatement{Token: p.curToken} + + p.nextToken() + + stmt.ReturnValue = p.parseExpression(LOWEST) + + if p.peekTokenIs(token.SEMICOLON) { + p.nextToken() + } + + return stmt +} + +func (p *Parser) parseExpressionStatement() *ast.ExpressionStatement { + stmt := &ast.ExpressionStatement{Token: p.curToken} + + stmt.Expression = p.parseExpression(LOWEST) + + if p.peekTokenIs(token.SEMICOLON) { + p.nextToken() + } + + return stmt +} + +func (p *Parser) parseExpression(precedence int) ast.Expression { + prefix := p.prefixParseFns[p.curToken.Type] + if prefix == nil { + p.noPrefixParseFnError(p.curToken.Type) + return nil + } + leftExp := prefix() + + for !p.peekTokenIs(token.SEMICOLON) && precedence < p.peekPrecedence() { + infix := p.infixParseFns[p.peekToken.Type] + if infix == nil { + return leftExp + } + + p.nextToken() + + leftExp = infix(leftExp) + } + + return leftExp +} + +func (p *Parser) peekPrecedence() int { + if p, ok := precedences[p.peekToken.Type]; ok { + return p + } + + return LOWEST +} + +func (p *Parser) curPrecedence() int { + if p, ok := precedences[p.curToken.Type]; ok { + return p + } + + return LOWEST +} + +func (p *Parser) parseIdentifier() ast.Expression { + return &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} +} + +func (p *Parser) parseIntegerLiteral() ast.Expression { + lit := &ast.IntegerLiteral{Token: p.curToken} + + value, err := strconv.ParseInt(p.curToken.Literal, 0, 64) + if err != nil { + msg := fmt.Sprintf("could not parse %q as integer", p.curToken.Literal) + p.errors = append(p.errors, msg) + return nil + } + + lit.Value = value + + return lit +} + +func (p *Parser) parseStringLiteral() ast.Expression { + return &ast.StringLiteral{Token: p.curToken, Value: p.curToken.Literal} +} + +func (p *Parser) parsePrefixExpression() ast.Expression { + expression := &ast.PrefixExpression{ + Token: p.curToken, + Operator: p.curToken.Literal, + } + + p.nextToken() + + expression.Right = p.parseExpression(PREFIX) + + return expression +} + +func (p *Parser) parseInfixExpression(left ast.Expression) ast.Expression { + expression := &ast.InfixExpression{ + Token: p.curToken, + Operator: p.curToken.Literal, + Left: left, + } + + precedence := p.curPrecedence() + p.nextToken() + expression.Right = p.parseExpression(precedence) + + return expression +} + +func (p *Parser) parseBoolean() ast.Expression { + return &ast.Boolean{Token: p.curToken, Value: p.curTokenIs(token.TRUE)} +} + +func (p *Parser) parseGroupedExpression() ast.Expression { + p.nextToken() + + exp := p.parseExpression(LOWEST) + + if !p.expectPeek(token.RPAREN) { + return nil + } + + return exp +} + +func (p *Parser) parseIfExpression() ast.Expression { + expression := &ast.IfExpression{Token: p.curToken} + + if !p.expectPeek(token.LPAREN) { + return nil + } + + p.nextToken() + expression.Condition = p.parseExpression(LOWEST) + + if !p.expectPeek(token.RPAREN) { + return nil + } + + if !p.expectPeek(token.LBRACE) { + return nil + } + + expression.Consequence = p.parseBlockStatement() + + if p.peekTokenIs(token.ELSE) { + p.nextToken() + + if !p.expectPeek(token.LBRACE) { + return nil + } + + expression.Alternative = p.parseBlockStatement() + } + + return expression +} + +func (p *Parser) parseBlockStatement() *ast.BlockStatement { + block := &ast.BlockStatement{Token: p.curToken} + block.Statements = []ast.Statement{} + + p.nextToken() + + for !p.curTokenIs(token.RBRACE) && !p.curTokenIs(token.EOF) { + stmt := p.parseStatement() + if stmt != nil { + block.Statements = append(block.Statements, stmt) + } + p.nextToken() + } + + return block +} + +func (p *Parser) parseFunctionLiteral() ast.Expression { + lit := &ast.FunctionLiteral{Token: p.curToken} + + if !p.expectPeek(token.LPAREN) { + return nil + } + + lit.Parameters = p.parseFunctionParameters() + + if !p.expectPeek(token.LBRACE) { + return nil + } + + lit.Body = p.parseBlockStatement() + + return lit +} + +func (p *Parser) parseFunctionParameters() []*ast.Identifier { + identifiers := []*ast.Identifier{} + + if p.peekTokenIs(token.RPAREN) { + p.nextToken() + return identifiers + } + + p.nextToken() + + ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + identifiers = append(identifiers, ident) + + for p.peekTokenIs(token.COMMA) { + p.nextToken() + p.nextToken() + ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + identifiers = append(identifiers, ident) + } + + if !p.expectPeek(token.RPAREN) { + return nil + } + + return identifiers +} + +func (p *Parser) parseCallExpression(function ast.Expression) ast.Expression { + exp := &ast.CallExpression{Token: p.curToken, Function: function} + exp.Arguments = p.parseExpressionList(token.RPAREN) + return exp +} + +func (p *Parser) parseExpressionList(end token.TokenType) []ast.Expression { + list := []ast.Expression{} + + if p.peekTokenIs(end) { + p.nextToken() + return list + } + + p.nextToken() + list = append(list, p.parseExpression(LOWEST)) + + for p.peekTokenIs(token.COMMA) { + p.nextToken() + p.nextToken() + list = append(list, p.parseExpression(LOWEST)) + } + + if !p.expectPeek(end) { + return nil + } + + return list +} + +func (p *Parser) parseArrayLiteral() ast.Expression { + array := &ast.ArrayLiteral{Token: p.curToken} + + array.Elements = p.parseExpressionList(token.RBRACKET) + + return array +} + +func (p *Parser) parseIndexExpression(left ast.Expression) ast.Expression { + exp := &ast.IndexExpression{Token: p.curToken, Left: left} + + p.nextToken() + exp.Index = p.parseExpression(LOWEST) + + if !p.expectPeek(token.RBRACKET) { + return nil + } + + return exp +} + +func (p *Parser) parseHashLiteral() ast.Expression { + hash := &ast.HashLiteral{Token: p.curToken} + hash.Pairs = make(map[ast.Expression]ast.Expression) + + for !p.peekTokenIs(token.RBRACE) { + p.nextToken() + key := p.parseExpression(LOWEST) + + if !p.expectPeek(token.COLON) { + return nil + } + + p.nextToken() + value := p.parseExpression(LOWEST) + + hash.Pairs[key] = value + + if !p.peekTokenIs(token.RBRACE) && !p.expectPeek(token.COMMA) { + return nil + } + } + + if !p.expectPeek(token.RBRACE) { + return nil + } + + return hash +} + +func (p *Parser) registerPrefix(tokenType token.TokenType, fn prefixParseFn) { + p.prefixParseFns[tokenType] = fn +} + +func (p *Parser) registerInfix(tokenType token.TokenType, fn infixParseFn) { + p.infixParseFns[tokenType] = fn +} diff --git a/wcig_code_1_2/00/src/monkey/parser/parser_test.go b/wcig_code_1_2/00/src/monkey/parser/parser_test.go new file mode 100644 index 0000000..674487d --- /dev/null +++ b/wcig_code_1_2/00/src/monkey/parser/parser_test.go @@ -0,0 +1,1083 @@ +package parser + +import ( + "fmt" + "monkey/ast" + "monkey/lexer" + "testing" +) + +func TestLetStatements(t *testing.T) { + tests := []struct { + input string + expectedIdentifier string + expectedValue interface{} + }{ + {"let x = 5;", "x", 5}, + {"let y = true;", "y", true}, + {"let foobar = y;", "foobar", "y"}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain 1 statements. got=%d", + len(program.Statements)) + } + + stmt := program.Statements[0] + if !testLetStatement(t, stmt, tt.expectedIdentifier) { + return + } + + val := stmt.(*ast.LetStatement).Value + if !testLiteralExpression(t, val, tt.expectedValue) { + return + } + } +} + +func TestReturnStatements(t *testing.T) { + tests := []struct { + input string + expectedValue interface{} + }{ + {"return 5;", 5}, + {"return true;", true}, + {"return foobar;", "foobar"}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain 1 statements. got=%d", + len(program.Statements)) + } + + stmt := program.Statements[0] + returnStmt, ok := stmt.(*ast.ReturnStatement) + if !ok { + t.Fatalf("stmt not *ast.returnStatement. got=%T", stmt) + } + if returnStmt.TokenLiteral() != "return" { + t.Fatalf("returnStmt.TokenLiteral not 'return', got %q", + returnStmt.TokenLiteral()) + } + if testLiteralExpression(t, returnStmt.ReturnValue, tt.expectedValue) { + return + } + } +} + +func TestIdentifierExpression(t *testing.T) { + input := "foobar;" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program has not enough statements. got=%d", + len(program.Statements)) + } + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + ident, ok := stmt.Expression.(*ast.Identifier) + if !ok { + t.Fatalf("exp not *ast.Identifier. got=%T", stmt.Expression) + } + if ident.Value != "foobar" { + t.Errorf("ident.Value not %s. got=%s", "foobar", ident.Value) + } + if ident.TokenLiteral() != "foobar" { + t.Errorf("ident.TokenLiteral not %s. got=%s", "foobar", + ident.TokenLiteral()) + } +} + +func TestIntegerLiteralExpression(t *testing.T) { + input := "5;" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program has not enough statements. got=%d", + len(program.Statements)) + } + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + literal, ok := stmt.Expression.(*ast.IntegerLiteral) + if !ok { + t.Fatalf("exp not *ast.IntegerLiteral. got=%T", stmt.Expression) + } + if literal.Value != 5 { + t.Errorf("literal.Value not %d. got=%d", 5, literal.Value) + } + if literal.TokenLiteral() != "5" { + t.Errorf("literal.TokenLiteral not %s. got=%s", "5", + literal.TokenLiteral()) + } +} + +func TestParsingPrefixExpressions(t *testing.T) { + prefixTests := []struct { + input string + operator string + value interface{} + }{ + {"!5;", "!", 5}, + {"-15;", "-", 15}, + {"!foobar;", "!", "foobar"}, + {"-foobar;", "-", "foobar"}, + {"!true;", "!", true}, + {"!false;", "!", false}, + } + + for _, tt := range prefixTests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.PrefixExpression) + if !ok { + t.Fatalf("stmt is not ast.PrefixExpression. got=%T", stmt.Expression) + } + if exp.Operator != tt.operator { + t.Fatalf("exp.Operator is not '%s'. got=%s", + tt.operator, exp.Operator) + } + if !testLiteralExpression(t, exp.Right, tt.value) { + return + } + } +} + +func TestParsingInfixExpressions(t *testing.T) { + infixTests := []struct { + input string + leftValue interface{} + operator string + rightValue interface{} + }{ + {"5 + 5;", 5, "+", 5}, + {"5 - 5;", 5, "-", 5}, + {"5 * 5;", 5, "*", 5}, + {"5 / 5;", 5, "/", 5}, + {"5 > 5;", 5, ">", 5}, + {"5 < 5;", 5, "<", 5}, + {"5 == 5;", 5, "==", 5}, + {"5 != 5;", 5, "!=", 5}, + {"foobar + barfoo;", "foobar", "+", "barfoo"}, + {"foobar - barfoo;", "foobar", "-", "barfoo"}, + {"foobar * barfoo;", "foobar", "*", "barfoo"}, + {"foobar / barfoo;", "foobar", "/", "barfoo"}, + {"foobar > barfoo;", "foobar", ">", "barfoo"}, + {"foobar < barfoo;", "foobar", "<", "barfoo"}, + {"foobar == barfoo;", "foobar", "==", "barfoo"}, + {"foobar != barfoo;", "foobar", "!=", "barfoo"}, + {"true == true", true, "==", true}, + {"true != false", true, "!=", false}, + {"false == false", false, "==", false}, + } + + for _, tt := range infixTests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + if !testInfixExpression(t, stmt.Expression, tt.leftValue, + tt.operator, tt.rightValue) { + return + } + } +} + +func TestOperatorPrecedenceParsing(t *testing.T) { + tests := []struct { + input string + expected string + }{ + { + "-a * b", + "((-a) * b)", + }, + { + "!-a", + "(!(-a))", + }, + { + "a + b + c", + "((a + b) + c)", + }, + { + "a + b - c", + "((a + b) - c)", + }, + { + "a * b * c", + "((a * b) * c)", + }, + { + "a * b / c", + "((a * b) / c)", + }, + { + "a + b / c", + "(a + (b / c))", + }, + { + "a + b * c + d / e - f", + "(((a + (b * c)) + (d / e)) - f)", + }, + { + "3 + 4; -5 * 5", + "(3 + 4)((-5) * 5)", + }, + { + "5 > 4 == 3 < 4", + "((5 > 4) == (3 < 4))", + }, + { + "5 < 4 != 3 > 4", + "((5 < 4) != (3 > 4))", + }, + { + "3 + 4 * 5 == 3 * 1 + 4 * 5", + "((3 + (4 * 5)) == ((3 * 1) + (4 * 5)))", + }, + { + "true", + "true", + }, + { + "false", + "false", + }, + { + "3 > 5 == false", + "((3 > 5) == false)", + }, + { + "3 < 5 == true", + "((3 < 5) == true)", + }, + { + "1 + (2 + 3) + 4", + "((1 + (2 + 3)) + 4)", + }, + { + "(5 + 5) * 2", + "((5 + 5) * 2)", + }, + { + "2 / (5 + 5)", + "(2 / (5 + 5))", + }, + { + "(5 + 5) * 2 * (5 + 5)", + "(((5 + 5) * 2) * (5 + 5))", + }, + { + "-(5 + 5)", + "(-(5 + 5))", + }, + { + "!(true == true)", + "(!(true == true))", + }, + { + "a + add(b * c) + d", + "((a + add((b * c))) + d)", + }, + { + "add(a, b, 1, 2 * 3, 4 + 5, add(6, 7 * 8))", + "add(a, b, 1, (2 * 3), (4 + 5), add(6, (7 * 8)))", + }, + { + "add(a + b + c * d / f + g)", + "add((((a + b) + ((c * d) / f)) + g))", + }, + { + "a * [1, 2, 3, 4][b * c] * d", + "((a * ([1, 2, 3, 4][(b * c)])) * d)", + }, + { + "add(a * b[2], b[1], 2 * [1, 2][1])", + "add((a * (b[2])), (b[1]), (2 * ([1, 2][1])))", + }, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + actual := program.String() + if actual != tt.expected { + t.Errorf("expected=%q, got=%q", tt.expected, actual) + } + } +} + +func TestBooleanExpression(t *testing.T) { + tests := []struct { + input string + expectedBoolean bool + }{ + {"true;", true}, + {"false;", false}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program has not enough statements. got=%d", + len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + boolean, ok := stmt.Expression.(*ast.Boolean) + if !ok { + t.Fatalf("exp not *ast.Boolean. got=%T", stmt.Expression) + } + if boolean.Value != tt.expectedBoolean { + t.Errorf("boolean.Value not %t. got=%t", tt.expectedBoolean, + boolean.Value) + } + } +} + +func TestIfExpression(t *testing.T) { + input := `if (x < y) { x }` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.IfExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.IfExpression. got=%T", + stmt.Expression) + } + + if !testInfixExpression(t, exp.Condition, "x", "<", "y") { + return + } + + if len(exp.Consequence.Statements) != 1 { + t.Errorf("consequence is not 1 statements. got=%d\n", + len(exp.Consequence.Statements)) + } + + consequence, ok := exp.Consequence.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", + exp.Consequence.Statements[0]) + } + + if !testIdentifier(t, consequence.Expression, "x") { + return + } + + if exp.Alternative != nil { + t.Errorf("exp.Alternative.Statements was not nil. got=%+v", exp.Alternative) + } +} + +func TestIfElseExpression(t *testing.T) { + input := `if (x < y) { x } else { y }` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.IfExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.IfExpression. got=%T", stmt.Expression) + } + + if !testInfixExpression(t, exp.Condition, "x", "<", "y") { + return + } + + if len(exp.Consequence.Statements) != 1 { + t.Errorf("consequence is not 1 statements. got=%d\n", + len(exp.Consequence.Statements)) + } + + consequence, ok := exp.Consequence.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", + exp.Consequence.Statements[0]) + } + + if !testIdentifier(t, consequence.Expression, "x") { + return + } + + if len(exp.Alternative.Statements) != 1 { + t.Errorf("exp.Alternative.Statements does not contain 1 statements. got=%d\n", + len(exp.Alternative.Statements)) + } + + alternative, ok := exp.Alternative.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", + exp.Alternative.Statements[0]) + } + + if !testIdentifier(t, alternative.Expression, "y") { + return + } +} + +func TestFunctionLiteralParsing(t *testing.T) { + input := `fn(x, y) { x + y; }` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + function, ok := stmt.Expression.(*ast.FunctionLiteral) + if !ok { + t.Fatalf("stmt.Expression is not ast.FunctionLiteral. got=%T", + stmt.Expression) + } + + if len(function.Parameters) != 2 { + t.Fatalf("function literal parameters wrong. want 2, got=%d\n", + len(function.Parameters)) + } + + testLiteralExpression(t, function.Parameters[0], "x") + testLiteralExpression(t, function.Parameters[1], "y") + + if len(function.Body.Statements) != 1 { + t.Fatalf("function.Body.Statements has not 1 statements. got=%d\n", + len(function.Body.Statements)) + } + + bodyStmt, ok := function.Body.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("function body stmt is not ast.ExpressionStatement. got=%T", + function.Body.Statements[0]) + } + + testInfixExpression(t, bodyStmt.Expression, "x", "+", "y") +} + +func TestFunctionParameterParsing(t *testing.T) { + tests := []struct { + input string + expectedParams []string + }{ + {input: "fn() {};", expectedParams: []string{}}, + {input: "fn(x) {};", expectedParams: []string{"x"}}, + {input: "fn(x, y, z) {};", expectedParams: []string{"x", "y", "z"}}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + function := stmt.Expression.(*ast.FunctionLiteral) + + if len(function.Parameters) != len(tt.expectedParams) { + t.Errorf("length parameters wrong. want %d, got=%d\n", + len(tt.expectedParams), len(function.Parameters)) + } + + for i, ident := range tt.expectedParams { + testLiteralExpression(t, function.Parameters[i], ident) + } + } +} + +func TestCallExpressionParsing(t *testing.T) { + input := "add(1, 2 * 3, 4 + 5);" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("stmt is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.CallExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.CallExpression. got=%T", + stmt.Expression) + } + + if !testIdentifier(t, exp.Function, "add") { + return + } + + if len(exp.Arguments) != 3 { + t.Fatalf("wrong length of arguments. got=%d", len(exp.Arguments)) + } + + testLiteralExpression(t, exp.Arguments[0], 1) + testInfixExpression(t, exp.Arguments[1], 2, "*", 3) + testInfixExpression(t, exp.Arguments[2], 4, "+", 5) +} + +func TestCallExpressionParameterParsing(t *testing.T) { + tests := []struct { + input string + expectedIdent string + expectedArgs []string + }{ + { + input: "add();", + expectedIdent: "add", + expectedArgs: []string{}, + }, + { + input: "add(1);", + expectedIdent: "add", + expectedArgs: []string{"1"}, + }, + { + input: "add(1, 2 * 3, 4 + 5);", + expectedIdent: "add", + expectedArgs: []string{"1", "(2 * 3)", "(4 + 5)"}, + }, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + exp, ok := stmt.Expression.(*ast.CallExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.CallExpression. got=%T", + stmt.Expression) + } + + if !testIdentifier(t, exp.Function, tt.expectedIdent) { + return + } + + if len(exp.Arguments) != len(tt.expectedArgs) { + t.Fatalf("wrong number of arguments. want=%d, got=%d", + len(tt.expectedArgs), len(exp.Arguments)) + } + + for i, arg := range tt.expectedArgs { + if exp.Arguments[i].String() != arg { + t.Errorf("argument %d wrong. want=%q, got=%q", i, + arg, exp.Arguments[i].String()) + } + } + } +} + +func TestStringLiteralExpression(t *testing.T) { + input := `"hello world";` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + literal, ok := stmt.Expression.(*ast.StringLiteral) + if !ok { + t.Fatalf("exp not *ast.StringLiteral. got=%T", stmt.Expression) + } + + if literal.Value != "hello world" { + t.Errorf("literal.Value not %q. got=%q", "hello world", literal.Value) + } +} + +func TestParsingEmptyArrayLiterals(t *testing.T) { + input := "[]" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + array, ok := stmt.Expression.(*ast.ArrayLiteral) + if !ok { + t.Fatalf("exp not ast.ArrayLiteral. got=%T", stmt.Expression) + } + + if len(array.Elements) != 0 { + t.Errorf("len(array.Elements) not 0. got=%d", len(array.Elements)) + } +} + +func TestParsingArrayLiterals(t *testing.T) { + input := "[1, 2 * 2, 3 + 3]" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + array, ok := stmt.Expression.(*ast.ArrayLiteral) + if !ok { + t.Fatalf("exp not ast.ArrayLiteral. got=%T", stmt.Expression) + } + + if len(array.Elements) != 3 { + t.Fatalf("len(array.Elements) not 3. got=%d", len(array.Elements)) + } + + testIntegerLiteral(t, array.Elements[0], 1) + testInfixExpression(t, array.Elements[1], 2, "*", 2) + testInfixExpression(t, array.Elements[2], 3, "+", 3) +} + +func TestParsingIndexExpressions(t *testing.T) { + input := "myArray[1 + 1]" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + indexExp, ok := stmt.Expression.(*ast.IndexExpression) + if !ok { + t.Fatalf("exp not *ast.IndexExpression. got=%T", stmt.Expression) + } + + if !testIdentifier(t, indexExp.Left, "myArray") { + return + } + + if !testInfixExpression(t, indexExp.Index, 1, "+", 1) { + return + } +} + +func TestParsingEmptyHashLiteral(t *testing.T) { + input := "{}" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + if len(hash.Pairs) != 0 { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } +} + +func TestParsingHashLiteralsStringKeys(t *testing.T) { + input := `{"one": 1, "two": 2, "three": 3}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + expected := map[string]int64{ + "one": 1, + "two": 2, + "three": 3, + } + + if len(hash.Pairs) != len(expected) { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } + + for key, value := range hash.Pairs { + literal, ok := key.(*ast.StringLiteral) + if !ok { + t.Errorf("key is not ast.StringLiteral. got=%T", key) + continue + } + + expectedValue := expected[literal.String()] + testIntegerLiteral(t, value, expectedValue) + } +} + +func TestParsingHashLiteralsBooleanKeys(t *testing.T) { + input := `{true: 1, false: 2}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + expected := map[string]int64{ + "true": 1, + "false": 2, + } + + if len(hash.Pairs) != len(expected) { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } + + for key, value := range hash.Pairs { + boolean, ok := key.(*ast.Boolean) + if !ok { + t.Errorf("key is not ast.BooleanLiteral. got=%T", key) + continue + } + + expectedValue := expected[boolean.String()] + testIntegerLiteral(t, value, expectedValue) + } +} + +func TestParsingHashLiteralsIntegerKeys(t *testing.T) { + input := `{1: 1, 2: 2, 3: 3}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + expected := map[string]int64{ + "1": 1, + "2": 2, + "3": 3, + } + + if len(hash.Pairs) != len(expected) { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } + + for key, value := range hash.Pairs { + integer, ok := key.(*ast.IntegerLiteral) + if !ok { + t.Errorf("key is not ast.IntegerLiteral. got=%T", key) + continue + } + + expectedValue := expected[integer.String()] + + testIntegerLiteral(t, value, expectedValue) + } +} + +func TestParsingHashLiteralsWithExpressions(t *testing.T) { + input := `{"one": 0 + 1, "two": 10 - 8, "three": 15 / 5}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + if len(hash.Pairs) != 3 { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } + + tests := map[string]func(ast.Expression){ + "one": func(e ast.Expression) { + testInfixExpression(t, e, 0, "+", 1) + }, + "two": func(e ast.Expression) { + testInfixExpression(t, e, 10, "-", 8) + }, + "three": func(e ast.Expression) { + testInfixExpression(t, e, 15, "/", 5) + }, + } + + for key, value := range hash.Pairs { + literal, ok := key.(*ast.StringLiteral) + if !ok { + t.Errorf("key is not ast.StringLiteral. got=%T", key) + continue + } + + testFunc, ok := tests[literal.String()] + if !ok { + t.Errorf("No test function for key %q found", literal.String()) + continue + } + + testFunc(value) + } +} + +func testLetStatement(t *testing.T, s ast.Statement, name string) bool { + if s.TokenLiteral() != "let" { + t.Errorf("s.TokenLiteral not 'let'. got=%q", s.TokenLiteral()) + return false + } + + letStmt, ok := s.(*ast.LetStatement) + if !ok { + t.Errorf("s not *ast.LetStatement. got=%T", s) + return false + } + + if letStmt.Name.Value != name { + t.Errorf("letStmt.Name.Value not '%s'. got=%s", name, letStmt.Name.Value) + return false + } + + if letStmt.Name.TokenLiteral() != name { + t.Errorf("letStmt.Name.TokenLiteral() not '%s'. got=%s", + name, letStmt.Name.TokenLiteral()) + return false + } + + return true +} + +func testInfixExpression(t *testing.T, exp ast.Expression, left interface{}, + operator string, right interface{}) bool { + + opExp, ok := exp.(*ast.InfixExpression) + if !ok { + t.Errorf("exp is not ast.InfixExpression. got=%T(%s)", exp, exp) + return false + } + + if !testLiteralExpression(t, opExp.Left, left) { + return false + } + + if opExp.Operator != operator { + t.Errorf("exp.Operator is not '%s'. got=%q", operator, opExp.Operator) + return false + } + + if !testLiteralExpression(t, opExp.Right, right) { + return false + } + + return true +} + +func testLiteralExpression( + t *testing.T, + exp ast.Expression, + expected interface{}, +) bool { + switch v := expected.(type) { + case int: + return testIntegerLiteral(t, exp, int64(v)) + case int64: + return testIntegerLiteral(t, exp, v) + case string: + return testIdentifier(t, exp, v) + case bool: + return testBooleanLiteral(t, exp, v) + } + t.Errorf("type of exp not handled. got=%T", exp) + return false +} + +func testIntegerLiteral(t *testing.T, il ast.Expression, value int64) bool { + integ, ok := il.(*ast.IntegerLiteral) + if !ok { + t.Errorf("il not *ast.IntegerLiteral. got=%T", il) + return false + } + + if integ.Value != value { + t.Errorf("integ.Value not %d. got=%d", value, integ.Value) + return false + } + + if integ.TokenLiteral() != fmt.Sprintf("%d", value) { + t.Errorf("integ.TokenLiteral not %d. got=%s", value, + integ.TokenLiteral()) + return false + } + + return true +} + +func testIdentifier(t *testing.T, exp ast.Expression, value string) bool { + ident, ok := exp.(*ast.Identifier) + if !ok { + t.Errorf("exp not *ast.Identifier. got=%T", exp) + return false + } + + if ident.Value != value { + t.Errorf("ident.Value not %s. got=%s", value, ident.Value) + return false + } + + if ident.TokenLiteral() != value { + t.Errorf("ident.TokenLiteral not %s. got=%s", value, + ident.TokenLiteral()) + return false + } + + return true +} + +func testBooleanLiteral(t *testing.T, exp ast.Expression, value bool) bool { + bo, ok := exp.(*ast.Boolean) + if !ok { + t.Errorf("exp not *ast.Boolean. got=%T", exp) + return false + } + + if bo.Value != value { + t.Errorf("bo.Value not %t. got=%t", value, bo.Value) + return false + } + + if bo.TokenLiteral() != fmt.Sprintf("%t", value) { + t.Errorf("bo.TokenLiteral not %t. got=%s", + value, bo.TokenLiteral()) + return false + } + + return true +} + +func checkParserErrors(t *testing.T, p *Parser) { + errors := p.Errors() + if len(errors) == 0 { + return + } + + t.Errorf("parser has %d errors", len(errors)) + for _, msg := range errors { + t.Errorf("parser error: %q", msg) + } + t.FailNow() +} diff --git a/wcig_code_1_2/00/src/monkey/parser/parser_tracing.go b/wcig_code_1_2/00/src/monkey/parser/parser_tracing.go new file mode 100644 index 0000000..5fc569b --- /dev/null +++ b/wcig_code_1_2/00/src/monkey/parser/parser_tracing.go @@ -0,0 +1,32 @@ +package parser + +import ( + "fmt" + "strings" +) + +var traceLevel int = 0 + +const traceIdentPlaceholder string = "\t" + +func identLevel() string { + return strings.Repeat(traceIdentPlaceholder, traceLevel-1) +} + +func tracePrint(fs string) { + fmt.Printf("%s%s\n", identLevel(), fs) +} + +func incIdent() { traceLevel = traceLevel + 1 } +func decIdent() { traceLevel = traceLevel - 1 } + +func trace(msg string) string { + incIdent() + tracePrint("BEGIN " + msg) + return msg +} + +func untrace(msg string) { + tracePrint("END " + msg) + decIdent() +} diff --git a/wcig_code_1_2/00/src/monkey/repl/repl.go b/wcig_code_1_2/00/src/monkey/repl/repl.go new file mode 100644 index 0000000..82287ab --- /dev/null +++ b/wcig_code_1_2/00/src/monkey/repl/repl.go @@ -0,0 +1,64 @@ +package repl + +import ( + "bufio" + "fmt" + "io" + "monkey/evaluator" + "monkey/lexer" + "monkey/object" + "monkey/parser" +) + +const PROMPT = ">> " + +func Start(in io.Reader, out io.Writer) { + scanner := bufio.NewScanner(in) + env := object.NewEnvironment() + + for { + fmt.Fprintf(out, PROMPT) + scanned := scanner.Scan() + if !scanned { + return + } + + line := scanner.Text() + l := lexer.New(line) + p := parser.New(l) + + program := p.ParseProgram() + if len(p.Errors()) != 0 { + printParserErrors(out, p.Errors()) + continue + } + + evaluated := evaluator.Eval(program, env) + if evaluated != nil { + io.WriteString(out, evaluated.Inspect()) + io.WriteString(out, "\n") + } + } +} + +const MONKEY_FACE = ` __,__ + .--. .-" "-. .--. + / .. \/ .-. .-. \/ .. \ + | | '| / Y \ |' | | + | \ \ \ 0 | 0 / / / | + \ '- ,\.-"""""""-./, -' / + ''-' /_ ^ ^ _\ '-'' + | \._ _./ | + \ \ '~' / / + '._ '-=-' _.' + '-----' +` + +func printParserErrors(out io.Writer, errors []string) { + io.WriteString(out, MONKEY_FACE) + io.WriteString(out, "Woops! We ran into some monkey business here!\n") + io.WriteString(out, " parser errors:\n") + for _, msg := range errors { + io.WriteString(out, "\t"+msg+"\n") + } +} diff --git a/wcig_code_1_2/00/src/monkey/token/token.go b/wcig_code_1_2/00/src/monkey/token/token.go new file mode 100644 index 0000000..3d2d2f7 --- /dev/null +++ b/wcig_code_1_2/00/src/monkey/token/token.go @@ -0,0 +1,70 @@ +package token + +type TokenType string + +const ( + ILLEGAL = "ILLEGAL" + EOF = "EOF" + + // Identifiers + literals + IDENT = "IDENT" // add, foobar, x, y, ... + INT = "INT" // 1343456 + STRING = "STRING" // "foobar" + + // Operators + ASSIGN = "=" + PLUS = "+" + MINUS = "-" + BANG = "!" + ASTERISK = "*" + SLASH = "/" + + LT = "<" + GT = ">" + + EQ = "==" + NOT_EQ = "!=" + + // Delimiters + COMMA = "," + SEMICOLON = ";" + COLON = ":" + + LPAREN = "(" + RPAREN = ")" + LBRACE = "{" + RBRACE = "}" + LBRACKET = "[" + RBRACKET = "]" + + // Keywords + FUNCTION = "FUNCTION" + LET = "LET" + TRUE = "TRUE" + FALSE = "FALSE" + IF = "IF" + ELSE = "ELSE" + RETURN = "RETURN" +) + +type Token struct { + Type TokenType + Literal string +} + +var keywords = map[string]TokenType{ + "fn": FUNCTION, + "let": LET, + "true": TRUE, + "false": FALSE, + "if": IF, + "else": ELSE, + "return": RETURN, +} + +func LookupIdent(ident string) TokenType { + if tok, ok := keywords[ident]; ok { + return tok + } + return IDENT +} diff --git a/wcig_code_1_2/02/.envrc b/wcig_code_1_2/02/.envrc new file mode 100644 index 0000000..fb65ef4 --- /dev/null +++ b/wcig_code_1_2/02/.envrc @@ -0,0 +1 @@ +export GOPATH=$(pwd) diff --git a/wcig_code_1_2/02/src/monkey/ast/ast.go b/wcig_code_1_2/02/src/monkey/ast/ast.go new file mode 100644 index 0000000..fb30b05 --- /dev/null +++ b/wcig_code_1_2/02/src/monkey/ast/ast.go @@ -0,0 +1,339 @@ +package ast + +import ( + "bytes" + "monkey/token" + "strings" +) + +// The base Node interface +type Node interface { + TokenLiteral() string + String() string +} + +// All statement nodes implement this +type Statement interface { + Node + statementNode() +} + +// All expression nodes implement this +type Expression interface { + Node + expressionNode() +} + +type Program struct { + Statements []Statement +} + +func (p *Program) TokenLiteral() string { + if len(p.Statements) > 0 { + return p.Statements[0].TokenLiteral() + } else { + return "" + } +} + +func (p *Program) String() string { + var out bytes.Buffer + + for _, s := range p.Statements { + out.WriteString(s.String()) + } + + return out.String() +} + +// Statements +type LetStatement struct { + Token token.Token // the token.LET token + Name *Identifier + Value Expression +} + +func (ls *LetStatement) statementNode() {} +func (ls *LetStatement) TokenLiteral() string { return ls.Token.Literal } +func (ls *LetStatement) String() string { + var out bytes.Buffer + + out.WriteString(ls.TokenLiteral() + " ") + out.WriteString(ls.Name.String()) + out.WriteString(" = ") + + if ls.Value != nil { + out.WriteString(ls.Value.String()) + } + + out.WriteString(";") + + return out.String() +} + +type ReturnStatement struct { + Token token.Token // the 'return' token + ReturnValue Expression +} + +func (rs *ReturnStatement) statementNode() {} +func (rs *ReturnStatement) TokenLiteral() string { return rs.Token.Literal } +func (rs *ReturnStatement) String() string { + var out bytes.Buffer + + out.WriteString(rs.TokenLiteral() + " ") + + if rs.ReturnValue != nil { + out.WriteString(rs.ReturnValue.String()) + } + + out.WriteString(";") + + return out.String() +} + +type ExpressionStatement struct { + Token token.Token // the first token of the expression + Expression Expression +} + +func (es *ExpressionStatement) statementNode() {} +func (es *ExpressionStatement) TokenLiteral() string { return es.Token.Literal } +func (es *ExpressionStatement) String() string { + if es.Expression != nil { + return es.Expression.String() + } + return "" +} + +type BlockStatement struct { + Token token.Token // the { token + Statements []Statement +} + +func (bs *BlockStatement) statementNode() {} +func (bs *BlockStatement) TokenLiteral() string { return bs.Token.Literal } +func (bs *BlockStatement) String() string { + var out bytes.Buffer + + for _, s := range bs.Statements { + out.WriteString(s.String()) + } + + return out.String() +} + +// Expressions +type Identifier struct { + Token token.Token // the token.IDENT token + Value string +} + +func (i *Identifier) expressionNode() {} +func (i *Identifier) TokenLiteral() string { return i.Token.Literal } +func (i *Identifier) String() string { return i.Value } + +type Boolean struct { + Token token.Token + Value bool +} + +func (b *Boolean) expressionNode() {} +func (b *Boolean) TokenLiteral() string { return b.Token.Literal } +func (b *Boolean) String() string { return b.Token.Literal } + +type IntegerLiteral struct { + Token token.Token + Value int64 +} + +func (il *IntegerLiteral) expressionNode() {} +func (il *IntegerLiteral) TokenLiteral() string { return il.Token.Literal } +func (il *IntegerLiteral) String() string { return il.Token.Literal } + +type PrefixExpression struct { + Token token.Token // The prefix token, e.g. ! + Operator string + Right Expression +} + +func (pe *PrefixExpression) expressionNode() {} +func (pe *PrefixExpression) TokenLiteral() string { return pe.Token.Literal } +func (pe *PrefixExpression) String() string { + var out bytes.Buffer + + out.WriteString("(") + out.WriteString(pe.Operator) + out.WriteString(pe.Right.String()) + out.WriteString(")") + + return out.String() +} + +type InfixExpression struct { + Token token.Token // The operator token, e.g. + + Left Expression + Operator string + Right Expression +} + +func (oe *InfixExpression) expressionNode() {} +func (oe *InfixExpression) TokenLiteral() string { return oe.Token.Literal } +func (oe *InfixExpression) String() string { + var out bytes.Buffer + + out.WriteString("(") + out.WriteString(oe.Left.String()) + out.WriteString(" " + oe.Operator + " ") + out.WriteString(oe.Right.String()) + out.WriteString(")") + + return out.String() +} + +type IfExpression struct { + Token token.Token // The 'if' token + Condition Expression + Consequence *BlockStatement + Alternative *BlockStatement +} + +func (ie *IfExpression) expressionNode() {} +func (ie *IfExpression) TokenLiteral() string { return ie.Token.Literal } +func (ie *IfExpression) String() string { + var out bytes.Buffer + + out.WriteString("if") + out.WriteString(ie.Condition.String()) + out.WriteString(" ") + out.WriteString(ie.Consequence.String()) + + if ie.Alternative != nil { + out.WriteString("else ") + out.WriteString(ie.Alternative.String()) + } + + return out.String() +} + +type FunctionLiteral struct { + Token token.Token // The 'fn' token + Parameters []*Identifier + Body *BlockStatement +} + +func (fl *FunctionLiteral) expressionNode() {} +func (fl *FunctionLiteral) TokenLiteral() string { return fl.Token.Literal } +func (fl *FunctionLiteral) String() string { + var out bytes.Buffer + + params := []string{} + for _, p := range fl.Parameters { + params = append(params, p.String()) + } + + out.WriteString(fl.TokenLiteral()) + out.WriteString("(") + out.WriteString(strings.Join(params, ", ")) + out.WriteString(") ") + out.WriteString(fl.Body.String()) + + return out.String() +} + +type CallExpression struct { + Token token.Token // The '(' token + Function Expression // Identifier or FunctionLiteral + Arguments []Expression +} + +func (ce *CallExpression) expressionNode() {} +func (ce *CallExpression) TokenLiteral() string { return ce.Token.Literal } +func (ce *CallExpression) String() string { + var out bytes.Buffer + + args := []string{} + for _, a := range ce.Arguments { + args = append(args, a.String()) + } + + out.WriteString(ce.Function.String()) + out.WriteString("(") + out.WriteString(strings.Join(args, ", ")) + out.WriteString(")") + + return out.String() +} + +type StringLiteral struct { + Token token.Token + Value string +} + +func (sl *StringLiteral) expressionNode() {} +func (sl *StringLiteral) TokenLiteral() string { return sl.Token.Literal } +func (sl *StringLiteral) String() string { return sl.Token.Literal } + +type ArrayLiteral struct { + Token token.Token // the '[' token + Elements []Expression +} + +func (al *ArrayLiteral) expressionNode() {} +func (al *ArrayLiteral) TokenLiteral() string { return al.Token.Literal } +func (al *ArrayLiteral) String() string { + var out bytes.Buffer + + elements := []string{} + for _, el := range al.Elements { + elements = append(elements, el.String()) + } + + out.WriteString("[") + out.WriteString(strings.Join(elements, ", ")) + out.WriteString("]") + + return out.String() +} + +type IndexExpression struct { + Token token.Token // The [ token + Left Expression + Index Expression +} + +func (ie *IndexExpression) expressionNode() {} +func (ie *IndexExpression) TokenLiteral() string { return ie.Token.Literal } +func (ie *IndexExpression) String() string { + var out bytes.Buffer + + out.WriteString("(") + out.WriteString(ie.Left.String()) + out.WriteString("[") + out.WriteString(ie.Index.String()) + out.WriteString("])") + + return out.String() +} + +type HashLiteral struct { + Token token.Token // the '{' token + Pairs map[Expression]Expression +} + +func (hl *HashLiteral) expressionNode() {} +func (hl *HashLiteral) TokenLiteral() string { return hl.Token.Literal } +func (hl *HashLiteral) String() string { + var out bytes.Buffer + + pairs := []string{} + for key, value := range hl.Pairs { + pairs = append(pairs, key.String()+":"+value.String()) + } + + out.WriteString("{") + out.WriteString(strings.Join(pairs, ", ")) + out.WriteString("}") + + return out.String() +} diff --git a/wcig_code_1_2/02/src/monkey/ast/ast_test.go b/wcig_code_1_2/02/src/monkey/ast/ast_test.go new file mode 100644 index 0000000..14a49dc --- /dev/null +++ b/wcig_code_1_2/02/src/monkey/ast/ast_test.go @@ -0,0 +1,28 @@ +package ast + +import ( + "monkey/token" + "testing" +) + +func TestString(t *testing.T) { + program := &Program{ + Statements: []Statement{ + &LetStatement{ + Token: token.Token{Type: token.LET, Literal: "let"}, + Name: &Identifier{ + Token: token.Token{Type: token.IDENT, Literal: "myVar"}, + Value: "myVar", + }, + Value: &Identifier{ + Token: token.Token{Type: token.IDENT, Literal: "anotherVar"}, + Value: "anotherVar", + }, + }, + }, + } + + if program.String() != "let myVar = anotherVar;" { + t.Errorf("program.String() wrong. got=%q", program.String()) + } +} diff --git a/wcig_code_1_2/02/src/monkey/code/code.go b/wcig_code_1_2/02/src/monkey/code/code.go new file mode 100644 index 0000000..5793daf --- /dev/null +++ b/wcig_code_1_2/02/src/monkey/code/code.go @@ -0,0 +1,121 @@ +package code + +import ( + "bytes" + "encoding/binary" + "fmt" +) + +type Instructions []byte + +func (ins Instructions) String() string { + var out bytes.Buffer + + i := 0 + for i < len(ins) { + def, err := Lookup(ins[i]) + if err != nil { + fmt.Fprintf(&out, "ERROR: %s\n", err) + continue + } + + operands, read := ReadOperands(def, ins[i+1:]) + + fmt.Fprintf(&out, "%04d %s\n", i, ins.fmtInstruction(def, operands)) + + i += 1 + read + } + + return out.String() +} + +func (ins Instructions) fmtInstruction(def *Definition, operands []int) string { + operandCount := len(def.OperandWidths) + + if len(operands) != operandCount { + return fmt.Sprintf("ERROR: operand len %d does not match defined %d\n", + len(operands), operandCount) + } + + switch operandCount { + case 0: + return def.Name + case 1: + return fmt.Sprintf("%s %d", def.Name, operands[0]) + } + + return fmt.Sprintf("ERROR: unhandled operandCount for %s\n", def.Name) +} + +type Opcode byte + +const ( + OpConstant Opcode = iota + OpAdd +) + +type Definition struct { + Name string + OperandWidths []int +} + +var definitions = map[Opcode]*Definition{ + OpConstant: {"OpConstant", []int{2}}, + OpAdd: {"OpAdd", []int{}}, +} + +func Lookup(op byte) (*Definition, error) { + def, ok := definitions[Opcode(op)] + if !ok { + return nil, fmt.Errorf("opcode %d undefined", op) + } + + return def, nil +} + +func Make(op Opcode, operands ...int) []byte { + def, ok := definitions[op] + if !ok { + return []byte{} + } + + instructionLen := 1 + for _, w := range def.OperandWidths { + instructionLen += w + } + + instruction := make([]byte, instructionLen) + instruction[0] = byte(op) + + offset := 1 + for i, o := range operands { + width := def.OperandWidths[i] + switch width { + case 2: + binary.BigEndian.PutUint16(instruction[offset:], uint16(o)) + } + offset += width + } + + return instruction +} + +func ReadOperands(def *Definition, ins Instructions) ([]int, int) { + operands := make([]int, len(def.OperandWidths)) + offset := 0 + + for i, width := range def.OperandWidths { + switch width { + case 2: + operands[i] = int(ReadUint16(ins[offset:])) + } + + offset += width + } + + return operands, offset +} + +func ReadUint16(ins Instructions) uint16 { + return binary.BigEndian.Uint16(ins) +} diff --git a/wcig_code_1_2/02/src/monkey/code/code_test.go b/wcig_code_1_2/02/src/monkey/code/code_test.go new file mode 100644 index 0000000..a353025 --- /dev/null +++ b/wcig_code_1_2/02/src/monkey/code/code_test.go @@ -0,0 +1,83 @@ +package code + +import "testing" + +func TestMake(t *testing.T) { + tests := []struct { + op Opcode + operands []int + expected []byte + }{ + {OpConstant, []int{65534}, []byte{byte(OpConstant), 255, 254}}, + {OpAdd, []int{}, []byte{byte(OpAdd)}}, + } + + for _, tt := range tests { + instruction := Make(tt.op, tt.operands...) + + if len(instruction) != len(tt.expected) { + t.Errorf("instruction has wrong length. want=%d, got=%d", + len(tt.expected), len(instruction)) + } + + for i, b := range tt.expected { + if instruction[i] != tt.expected[i] { + t.Errorf("wrong byte at pos %d. want=%d, got=%d", + i, b, instruction[i]) + } + } + } +} + +func TestInstructionsString(t *testing.T) { + instructions := []Instructions{ + Make(OpAdd), + Make(OpConstant, 2), + Make(OpConstant, 65535), + } + + expected := `0000 OpAdd +0001 OpConstant 2 +0004 OpConstant 65535 +` + + concatted := Instructions{} + for _, ins := range instructions { + concatted = append(concatted, ins...) + } + + if concatted.String() != expected { + t.Errorf("instructions wrongly formatted.\nwant=%q\ngot=%q", + expected, concatted.String()) + } +} + +func TestReadOperands(t *testing.T) { + tests := []struct { + op Opcode + operands []int + bytesRead int + }{ + {OpConstant, []int{65535}, 2}, + } + + for _, tt := range tests { + instruction := Make(tt.op, tt.operands...) + + def, err := Lookup(byte(tt.op)) + if err != nil { + t.Fatalf("definition not found: %q\n", err) + } + + operandsRead, n := ReadOperands(def, instruction[1:]) + if n != tt.bytesRead { + t.Fatalf("n wrong. want=%d, got=%d", tt.bytesRead, n) + } + + for i, want := range tt.operands { + if operandsRead[i] != want { + t.Errorf("operand wrong. want=%d, got=%d", want, operandsRead[i]) + } + } + } +} diff --git a/wcig_code_1_2/02/src/monkey/compiler/compiler.go b/wcig_code_1_2/02/src/monkey/compiler/compiler.go new file mode 100644 index 0000000..e2d24f8 --- /dev/null +++ b/wcig_code_1_2/02/src/monkey/compiler/compiler.go @@ -0,0 +1,92 @@ +package compiler + +import ( + "fmt" + "monkey/ast" + "monkey/code" + "monkey/object" +) + +type Compiler struct { + instructions code.Instructions + constants []object.Object +} + +func New() *Compiler { + return &Compiler{ + instructions: code.Instructions{}, + constants: []object.Object{}, + } +} + +func (c *Compiler) Compile(node ast.Node) error { + switch node := node.(type) { + case *ast.Program: + for _, s := range node.Statements { + err := c.Compile(s) + if err != nil { + return err + } + } + + case *ast.ExpressionStatement: + err := c.Compile(node.Expression) + if err != nil { + return err + } + + case *ast.InfixExpression: + err := c.Compile(node.Left) + if err != nil { + return err + } + + err = c.Compile(node.Right) + if err != nil { + return err + } + + switch node.Operator { + case "+": + c.emit(code.OpAdd) + default: + return fmt.Errorf("unknown operator %s", node.Operator) + } + + case *ast.IntegerLiteral: + integer := &object.Integer{Value: node.Value} + c.emit(code.OpConstant, c.addConstant(integer)) + + } + + return nil +} + +func (c *Compiler) Bytecode() *Bytecode { + return &Bytecode{ + Instructions: c.instructions, + Constants: c.constants, + } +} + +func (c *Compiler) addConstant(obj object.Object) int { + c.constants = append(c.constants, obj) + return len(c.constants) - 1 +} + +func (c *Compiler) emit(op code.Opcode, operands ...int) int { + ins := code.Make(op, operands...) + pos := c.addInstruction(ins) + return pos +} + +func (c *Compiler) addInstruction(ins []byte) int { + posNewInstruction := len(c.instructions) + c.instructions = append(c.instructions, ins...) + return posNewInstruction +} + +type Bytecode struct { + Instructions code.Instructions + Constants []object.Object +} diff --git a/wcig_code_1_2/02/src/monkey/compiler/compiler_test.go b/wcig_code_1_2/02/src/monkey/compiler/compiler_test.go new file mode 100644 index 0000000..0a21bb4 --- /dev/null +++ b/wcig_code_1_2/02/src/monkey/compiler/compiler_test.go @@ -0,0 +1,135 @@ +package compiler + +import ( + "fmt" + "monkey/ast" + "monkey/code" + "monkey/lexer" + "monkey/object" + "monkey/parser" + "testing" +) + +func TestIntegerArithmetic(t *testing.T) { + tests := []compilerTestCase{ + { + input: "1 + 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpAdd), + }, + }, + } + + runCompilerTests(t, tests) +} + +type compilerTestCase struct { + input string + expectedConstants []interface{} + expectedInstructions []code.Instructions +} + +func runCompilerTests(t *testing.T, tests []compilerTestCase) { + t.Helper() + + for _, tt := range tests { + program := parse(tt.input) + + compiler := New() + err := compiler.Compile(program) + if err != nil { + t.Fatalf("compiler error: %s", err) + } + + bytecode := compiler.Bytecode() + + err = testInstructions(tt.expectedInstructions, bytecode.Instructions) + if err != nil { + t.Fatalf("testInstructions failed: %s", err) + } + + err = testConstants(t, tt.expectedConstants, bytecode.Constants) + if err != nil { + t.Fatalf("testConstants failed: %s", err) + } + } +} + +func parse(input string) *ast.Program { + l := lexer.New(input) + p := parser.New(l) + return p.ParseProgram() +} + +func testInstructions( + expected []code.Instructions, + actual code.Instructions, +) error { + concatted := concatInstructions(expected) + + if len(actual) != len(concatted) { + return fmt.Errorf("wrong instructions length.\nwant=%q\ngot =%q", + concatted, actual) + } + + for i, ins := range concatted { + if actual[i] != ins { + return fmt.Errorf("wrong instruction at %d.\nwant=%q\ngot =%q", + i, concatted, actual) + } + } + + return nil +} + +func concatInstructions(s []code.Instructions) code.Instructions { + out := code.Instructions{} + + for _, ins := range s { + out = append(out, ins...) + } + + return out +} + +func testConstants( + t *testing.T, + expected []interface{}, + actual []object.Object, +) error { + if len(expected) != len(actual) { + return fmt.Errorf("wrong number of constants. got=%d, want=%d", + len(actual), len(expected)) + } + + for i, constant := range expected { + switch constant := constant.(type) { + case int: + err := testIntegerObject(int64(constant), actual[i]) + if err != nil { + return fmt.Errorf("constant %d - testIntegerObject failed: %s", + i, err) + } + } + } + + return nil +} + +func testIntegerObject(expected int64, actual object.Object) error { + result, ok := actual.(*object.Integer) + if !ok { + return fmt.Errorf("object is not Integer. got=%T (%+v)", + actual, actual) + } + + if result.Value != expected { + return fmt.Errorf("object has wrong value. got=%d, want=%d", + result.Value, expected) + } + + return nil +} diff --git a/wcig_code_1_2/02/src/monkey/evaluator/builtins.go b/wcig_code_1_2/02/src/monkey/evaluator/builtins.go new file mode 100644 index 0000000..c4fb0f3 --- /dev/null +++ b/wcig_code_1_2/02/src/monkey/evaluator/builtins.go @@ -0,0 +1,117 @@ +package evaluator + +import ( + "fmt" + "monkey/object" +) + +var builtins = map[string]*object.Builtin{ + "len": &object.Builtin{Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", + len(args)) + } + + switch arg := args[0].(type) { + case *object.Array: + return &object.Integer{Value: int64(len(arg.Elements))} + case *object.String: + return &object.Integer{Value: int64(len(arg.Value))} + default: + return newError("argument to `len` not supported, got %s", + args[0].Type()) + } + }, + }, + "puts": &object.Builtin{ + Fn: func(args ...object.Object) object.Object { + for _, arg := range args { + fmt.Println(arg.Inspect()) + } + + return NULL + }, + }, + "first": &object.Builtin{ + Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", + len(args)) + } + if args[0].Type() != object.ARRAY_OBJ { + return newError("argument to `first` must be ARRAY, got %s", + args[0].Type()) + } + + arr := args[0].(*object.Array) + if len(arr.Elements) > 0 { + return arr.Elements[0] + } + + return NULL + }, + }, + "last": &object.Builtin{ + Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", + len(args)) + } + if args[0].Type() != object.ARRAY_OBJ { + return newError("argument to `last` must be ARRAY, got %s", + args[0].Type()) + } + + arr := args[0].(*object.Array) + length := len(arr.Elements) + if length > 0 { + return arr.Elements[length-1] + } + + return NULL + }, + }, + "rest": &object.Builtin{ + Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", + len(args)) + } + if args[0].Type() != object.ARRAY_OBJ { + return newError("argument to `rest` must be ARRAY, got %s", + args[0].Type()) + } + + arr := args[0].(*object.Array) + length := len(arr.Elements) + if length > 0 { + newElements := make([]object.Object, length-1, length-1) + copy(newElements, arr.Elements[1:length]) + return &object.Array{Elements: newElements} + } + + return NULL + }, + }, + "push": &object.Builtin{ + Fn: func(args ...object.Object) object.Object { + if len(args) != 2 { + return newError("wrong number of arguments. got=%d, want=2", + len(args)) + } + if args[0].Type() != object.ARRAY_OBJ { + return newError("argument to `push` must be ARRAY, got %s", + args[0].Type()) + } + + arr := args[0].(*object.Array) + length := len(arr.Elements) + + newElements := make([]object.Object, length+1, length+1) + copy(newElements, arr.Elements) + newElements[length] = args[1] + + return &object.Array{Elements: newElements} + }, + }, +} diff --git a/wcig_code_1_2/02/src/monkey/evaluator/evaluator.go b/wcig_code_1_2/02/src/monkey/evaluator/evaluator.go new file mode 100644 index 0000000..50b2ab5 --- /dev/null +++ b/wcig_code_1_2/02/src/monkey/evaluator/evaluator.go @@ -0,0 +1,442 @@ +package evaluator + +import ( + "fmt" + "monkey/ast" + "monkey/object" +) + +var ( + NULL = &object.Null{} + TRUE = &object.Boolean{Value: true} + FALSE = &object.Boolean{Value: false} +) + +func Eval(node ast.Node, env *object.Environment) object.Object { + switch node := node.(type) { + + // Statements + case *ast.Program: + return evalProgram(node, env) + + case *ast.BlockStatement: + return evalBlockStatement(node, env) + + case *ast.ExpressionStatement: + return Eval(node.Expression, env) + + case *ast.ReturnStatement: + val := Eval(node.ReturnValue, env) + if isError(val) { + return val + } + return &object.ReturnValue{Value: val} + + case *ast.LetStatement: + val := Eval(node.Value, env) + if isError(val) { + return val + } + env.Set(node.Name.Value, val) + + // Expressions + case *ast.IntegerLiteral: + return &object.Integer{Value: node.Value} + + case *ast.StringLiteral: + return &object.String{Value: node.Value} + + case *ast.Boolean: + return nativeBoolToBooleanObject(node.Value) + + case *ast.PrefixExpression: + right := Eval(node.Right, env) + if isError(right) { + return right + } + return evalPrefixExpression(node.Operator, right) + + case *ast.InfixExpression: + left := Eval(node.Left, env) + if isError(left) { + return left + } + + right := Eval(node.Right, env) + if isError(right) { + return right + } + + return evalInfixExpression(node.Operator, left, right) + + case *ast.IfExpression: + return evalIfExpression(node, env) + + case *ast.Identifier: + return evalIdentifier(node, env) + + case *ast.FunctionLiteral: + params := node.Parameters + body := node.Body + return &object.Function{Parameters: params, Env: env, Body: body} + + case *ast.CallExpression: + function := Eval(node.Function, env) + if isError(function) { + return function + } + + args := evalExpressions(node.Arguments, env) + if len(args) == 1 && isError(args[0]) { + return args[0] + } + + return applyFunction(function, args) + + case *ast.ArrayLiteral: + elements := evalExpressions(node.Elements, env) + if len(elements) == 1 && isError(elements[0]) { + return elements[0] + } + return &object.Array{Elements: elements} + + case *ast.IndexExpression: + left := Eval(node.Left, env) + if isError(left) { + return left + } + index := Eval(node.Index, env) + if isError(index) { + return index + } + return evalIndexExpression(left, index) + + case *ast.HashLiteral: + return evalHashLiteral(node, env) + + } + + return nil +} + +func evalProgram(program *ast.Program, env *object.Environment) object.Object { + var result object.Object + + for _, statement := range program.Statements { + result = Eval(statement, env) + + switch result := result.(type) { + case *object.ReturnValue: + return result.Value + case *object.Error: + return result + } + } + + return result +} + +func evalBlockStatement( + block *ast.BlockStatement, + env *object.Environment, +) object.Object { + var result object.Object + + for _, statement := range block.Statements { + result = Eval(statement, env) + + if result != nil { + rt := result.Type() + if rt == object.RETURN_VALUE_OBJ || rt == object.ERROR_OBJ { + return result + } + } + } + + return result +} + +func nativeBoolToBooleanObject(input bool) *object.Boolean { + if input { + return TRUE + } + return FALSE +} + +func evalPrefixExpression(operator string, right object.Object) object.Object { + switch operator { + case "!": + return evalBangOperatorExpression(right) + case "-": + return evalMinusPrefixOperatorExpression(right) + default: + return newError("unknown operator: %s%s", operator, right.Type()) + } +} + +func evalInfixExpression( + operator string, + left, right object.Object, +) object.Object { + switch { + case left.Type() == object.INTEGER_OBJ && right.Type() == object.INTEGER_OBJ: + return evalIntegerInfixExpression(operator, left, right) + case left.Type() == object.STRING_OBJ && right.Type() == object.STRING_OBJ: + return evalStringInfixExpression(operator, left, right) + case operator == "==": + return nativeBoolToBooleanObject(left == right) + case operator == "!=": + return nativeBoolToBooleanObject(left != right) + case left.Type() != right.Type(): + return newError("type mismatch: %s %s %s", + left.Type(), operator, right.Type()) + default: + return newError("unknown operator: %s %s %s", + left.Type(), operator, right.Type()) + } +} + +func evalBangOperatorExpression(right object.Object) object.Object { + switch right { + case TRUE: + return FALSE + case FALSE: + return TRUE + case NULL: + return TRUE + default: + return FALSE + } +} + +func evalMinusPrefixOperatorExpression(right object.Object) object.Object { + if right.Type() != object.INTEGER_OBJ { + return newError("unknown operator: -%s", right.Type()) + } + + value := right.(*object.Integer).Value + return &object.Integer{Value: -value} +} + +func evalIntegerInfixExpression( + operator string, + left, right object.Object, +) object.Object { + leftVal := left.(*object.Integer).Value + rightVal := right.(*object.Integer).Value + + switch operator { + case "+": + return &object.Integer{Value: leftVal + rightVal} + case "-": + return &object.Integer{Value: leftVal - rightVal} + case "*": + return &object.Integer{Value: leftVal * rightVal} + case "/": + return &object.Integer{Value: leftVal / rightVal} + case "<": + return nativeBoolToBooleanObject(leftVal < rightVal) + case ">": + return nativeBoolToBooleanObject(leftVal > rightVal) + case "==": + return nativeBoolToBooleanObject(leftVal == rightVal) + case "!=": + return nativeBoolToBooleanObject(leftVal != rightVal) + default: + return newError("unknown operator: %s %s %s", + left.Type(), operator, right.Type()) + } +} + +func evalStringInfixExpression( + operator string, + left, right object.Object, +) object.Object { + if operator != "+" { + return newError("unknown operator: %s %s %s", + left.Type(), operator, right.Type()) + } + + leftVal := left.(*object.String).Value + rightVal := right.(*object.String).Value + return &object.String{Value: leftVal + rightVal} +} + +func evalIfExpression( + ie *ast.IfExpression, + env *object.Environment, +) object.Object { + condition := Eval(ie.Condition, env) + if isError(condition) { + return condition + } + + if isTruthy(condition) { + return Eval(ie.Consequence, env) + } else if ie.Alternative != nil { + return Eval(ie.Alternative, env) + } else { + return NULL + } +} + +func evalIdentifier( + node *ast.Identifier, + env *object.Environment, +) object.Object { + if val, ok := env.Get(node.Value); ok { + return val + } + + if builtin, ok := builtins[node.Value]; ok { + return builtin + } + + return newError("identifier not found: " + node.Value) +} + +func isTruthy(obj object.Object) bool { + switch obj { + case NULL: + return false + case TRUE: + return true + case FALSE: + return false + default: + return true + } +} + +func newError(format string, a ...interface{}) *object.Error { + return &object.Error{Message: fmt.Sprintf(format, a...)} +} + +func isError(obj object.Object) bool { + if obj != nil { + return obj.Type() == object.ERROR_OBJ + } + return false +} + +func evalExpressions( + exps []ast.Expression, + env *object.Environment, +) []object.Object { + var result []object.Object + + for _, e := range exps { + evaluated := Eval(e, env) + if isError(evaluated) { + return []object.Object{evaluated} + } + result = append(result, evaluated) + } + + return result +} + +func applyFunction(fn object.Object, args []object.Object) object.Object { + switch fn := fn.(type) { + + case *object.Function: + extendedEnv := extendFunctionEnv(fn, args) + evaluated := Eval(fn.Body, extendedEnv) + return unwrapReturnValue(evaluated) + + case *object.Builtin: + return fn.Fn(args...) + + default: + return newError("not a function: %s", fn.Type()) + } +} + +func extendFunctionEnv( + fn *object.Function, + args []object.Object, +) *object.Environment { + env := object.NewEnclosedEnvironment(fn.Env) + + for paramIdx, param := range fn.Parameters { + env.Set(param.Value, args[paramIdx]) + } + + return env +} + +func unwrapReturnValue(obj object.Object) object.Object { + if returnValue, ok := obj.(*object.ReturnValue); ok { + return returnValue.Value + } + + return obj +} + +func evalIndexExpression(left, index object.Object) object.Object { + switch { + case left.Type() == object.ARRAY_OBJ && index.Type() == object.INTEGER_OBJ: + return evalArrayIndexExpression(left, index) + case left.Type() == object.HASH_OBJ: + return evalHashIndexExpression(left, index) + default: + return newError("index operator not supported: %s", left.Type()) + } +} + +func evalArrayIndexExpression(array, index object.Object) object.Object { + arrayObject := array.(*object.Array) + idx := index.(*object.Integer).Value + max := int64(len(arrayObject.Elements) - 1) + + if idx < 0 || idx > max { + return NULL + } + + return arrayObject.Elements[idx] +} + +func evalHashLiteral( + node *ast.HashLiteral, + env *object.Environment, +) object.Object { + pairs := make(map[object.HashKey]object.HashPair) + + for keyNode, valueNode := range node.Pairs { + key := Eval(keyNode, env) + if isError(key) { + return key + } + + hashKey, ok := key.(object.Hashable) + if !ok { + return newError("unusable as hash key: %s", key.Type()) + } + + value := Eval(valueNode, env) + if isError(value) { + return value + } + + hashed := hashKey.HashKey() + pairs[hashed] = object.HashPair{Key: key, Value: value} + } + + return &object.Hash{Pairs: pairs} +} + +func evalHashIndexExpression(hash, index object.Object) object.Object { + hashObject := hash.(*object.Hash) + + key, ok := index.(object.Hashable) + if !ok { + return newError("unusable as hash key: %s", index.Type()) + } + + pair, ok := hashObject.Pairs[key.HashKey()] + if !ok { + return NULL + } + + return pair.Value +} diff --git a/wcig_code_1_2/02/src/monkey/evaluator/evaluator_test.go b/wcig_code_1_2/02/src/monkey/evaluator/evaluator_test.go new file mode 100644 index 0000000..2304e6f --- /dev/null +++ b/wcig_code_1_2/02/src/monkey/evaluator/evaluator_test.go @@ -0,0 +1,629 @@ +package evaluator + +import ( + "monkey/lexer" + "monkey/object" + "monkey/parser" + "testing" +) + +func TestEvalIntegerExpression(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"5", 5}, + {"10", 10}, + {"-5", -5}, + {"-10", -10}, + {"5 + 5 + 5 + 5 - 10", 10}, + {"2 * 2 * 2 * 2 * 2", 32}, + {"-50 + 100 + -50", 0}, + {"5 * 2 + 10", 20}, + {"5 + 2 * 10", 25}, + {"20 + 2 * -10", 0}, + {"50 / 2 * 2 + 10", 60}, + {"2 * (5 + 10)", 30}, + {"3 * 3 * 3 + 10", 37}, + {"3 * (3 * 3) + 10", 37}, + {"(5 + 10 * 2 + 15 / 3) * 2 + -10", 50}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testIntegerObject(t, evaluated, tt.expected) + } +} + +func TestEvalBooleanExpression(t *testing.T) { + tests := []struct { + input string + expected bool + }{ + {"true", true}, + {"false", false}, + {"1 < 2", true}, + {"1 > 2", false}, + {"1 < 1", false}, + {"1 > 1", false}, + {"1 == 1", true}, + {"1 != 1", false}, + {"1 == 2", false}, + {"1 != 2", true}, + {"true == true", true}, + {"false == false", true}, + {"true == false", false}, + {"true != false", true}, + {"false != true", true}, + {"(1 < 2) == true", true}, + {"(1 < 2) == false", false}, + {"(1 > 2) == true", false}, + {"(1 > 2) == false", true}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testBooleanObject(t, evaluated, tt.expected) + } +} + +func TestBangOperator(t *testing.T) { + tests := []struct { + input string + expected bool + }{ + {"!true", false}, + {"!false", true}, + {"!5", false}, + {"!!true", true}, + {"!!false", false}, + {"!!5", true}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testBooleanObject(t, evaluated, tt.expected) + } +} + +func TestIfElseExpressions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + {"if (true) { 10 }", 10}, + {"if (false) { 10 }", nil}, + {"if (1) { 10 }", 10}, + {"if (1 < 2) { 10 }", 10}, + {"if (1 > 2) { 10 }", nil}, + {"if (1 > 2) { 10 } else { 20 }", 20}, + {"if (1 < 2) { 10 } else { 20 }", 10}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + integer, ok := tt.expected.(int) + if ok { + testIntegerObject(t, evaluated, int64(integer)) + } else { + testNullObject(t, evaluated) + } + } +} + +func TestReturnStatements(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"return 10;", 10}, + {"return 10; 9;", 10}, + {"return 2 * 5; 9;", 10}, + {"9; return 2 * 5; 9;", 10}, + {"if (10 > 1) { return 10; }", 10}, + { + ` +if (10 > 1) { + if (10 > 1) { + return 10; + } + + return 1; +} +`, + 10, + }, + { + ` +let f = fn(x) { + return x; + x + 10; +}; +f(10);`, + 10, + }, + { + ` +let f = fn(x) { + let result = x + 10; + return result; + return 10; +}; +f(10);`, + 20, + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testIntegerObject(t, evaluated, tt.expected) + } +} + +func TestErrorHandling(t *testing.T) { + tests := []struct { + input string + expectedMessage string + }{ + { + "5 + true;", + "type mismatch: INTEGER + BOOLEAN", + }, + { + "5 + true; 5;", + "type mismatch: INTEGER + BOOLEAN", + }, + { + "-true", + "unknown operator: -BOOLEAN", + }, + { + "true + false;", + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + "true + false + true + false;", + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + "5; true + false; 5", + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + `"Hello" - "World"`, + "unknown operator: STRING - STRING", + }, + { + "if (10 > 1) { true + false; }", + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + ` +if (10 > 1) { + if (10 > 1) { + return true + false; + } + + return 1; +} +`, + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + "foobar", + "identifier not found: foobar", + }, + { + `{"name": "Monkey"}[fn(x) { x }];`, + "unusable as hash key: FUNCTION", + }, + { + `999[1]`, + "index operator not supported: INTEGER", + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + + errObj, ok := evaluated.(*object.Error) + if !ok { + t.Errorf("no error object returned. got=%T(%+v)", + evaluated, evaluated) + continue + } + + if errObj.Message != tt.expectedMessage { + t.Errorf("wrong error message. expected=%q, got=%q", + tt.expectedMessage, errObj.Message) + } + } +} + +func TestLetStatements(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"let a = 5; a;", 5}, + {"let a = 5 * 5; a;", 25}, + {"let a = 5; let b = a; b;", 5}, + {"let a = 5; let b = a; let c = a + b + 5; c;", 15}, + } + + for _, tt := range tests { + testIntegerObject(t, testEval(tt.input), tt.expected) + } +} + +func TestFunctionObject(t *testing.T) { + input := "fn(x) { x + 2; };" + + evaluated := testEval(input) + fn, ok := evaluated.(*object.Function) + if !ok { + t.Fatalf("object is not Function. got=%T (%+v)", evaluated, evaluated) + } + + if len(fn.Parameters) != 1 { + t.Fatalf("function has wrong parameters. Parameters=%+v", + fn.Parameters) + } + + if fn.Parameters[0].String() != "x" { + t.Fatalf("parameter is not 'x'. got=%q", fn.Parameters[0]) + } + + expectedBody := "(x + 2)" + + if fn.Body.String() != expectedBody { + t.Fatalf("body is not %q. got=%q", expectedBody, fn.Body.String()) + } +} + +func TestFunctionApplication(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"let identity = fn(x) { x; }; identity(5);", 5}, + {"let identity = fn(x) { return x; }; identity(5);", 5}, + {"let double = fn(x) { x * 2; }; double(5);", 10}, + {"let add = fn(x, y) { x + y; }; add(5, 5);", 10}, + {"let add = fn(x, y) { x + y; }; add(5 + 5, add(5, 5));", 20}, + {"fn(x) { x; }(5)", 5}, + } + + for _, tt := range tests { + testIntegerObject(t, testEval(tt.input), tt.expected) + } +} + +func TestEnclosingEnvironments(t *testing.T) { + input := ` +let first = 10; +let second = 10; +let third = 10; + +let ourFunction = fn(first) { + let second = 20; + + first + second + third; +}; + +ourFunction(20) + first + second;` + + testIntegerObject(t, testEval(input), 70) +} + +func TestClosures(t *testing.T) { + input := ` +let newAdder = fn(x) { + fn(y) { x + y }; +}; + +let addTwo = newAdder(2); +addTwo(2);` + + testIntegerObject(t, testEval(input), 4) +} + +func TestStringLiteral(t *testing.T) { + input := `"Hello World!"` + + evaluated := testEval(input) + str, ok := evaluated.(*object.String) + if !ok { + t.Fatalf("object is not String. got=%T (%+v)", evaluated, evaluated) + } + + if str.Value != "Hello World!" { + t.Errorf("String has wrong value. got=%q", str.Value) + } +} + +func TestStringConcatenation(t *testing.T) { + input := `"Hello" + " " + "World!"` + + evaluated := testEval(input) + str, ok := evaluated.(*object.String) + if !ok { + t.Fatalf("object is not String. got=%T (%+v)", evaluated, evaluated) + } + + if str.Value != "Hello World!" { + t.Errorf("String has wrong value. got=%q", str.Value) + } +} + +func TestBuiltinFunctions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + {`len("")`, 0}, + {`len("four")`, 4}, + {`len("hello world")`, 11}, + {`len(1)`, "argument to `len` not supported, got INTEGER"}, + {`len("one", "two")`, "wrong number of arguments. got=2, want=1"}, + {`len([1, 2, 3])`, 3}, + {`len([])`, 0}, + {`puts("hello", "world!")`, nil}, + {`first([1, 2, 3])`, 1}, + {`first([])`, nil}, + {`first(1)`, "argument to `first` must be ARRAY, got INTEGER"}, + {`last([1, 2, 3])`, 3}, + {`last([])`, nil}, + {`last(1)`, "argument to `last` must be ARRAY, got INTEGER"}, + {`rest([1, 2, 3])`, []int{2, 3}}, + {`rest([])`, nil}, + {`push([], 1)`, []int{1}}, + {`push(1, 1)`, "argument to `push` must be ARRAY, got INTEGER"}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + + switch expected := tt.expected.(type) { + case int: + testIntegerObject(t, evaluated, int64(expected)) + case nil: + testNullObject(t, evaluated) + case string: + errObj, ok := evaluated.(*object.Error) + if !ok { + t.Errorf("object is not Error. got=%T (%+v)", + evaluated, evaluated) + continue + } + if errObj.Message != expected { + t.Errorf("wrong error message. expected=%q, got=%q", + expected, errObj.Message) + } + case []int: + array, ok := evaluated.(*object.Array) + if !ok { + t.Errorf("obj not Array. got=%T (%+v)", evaluated, evaluated) + continue + } + + if len(array.Elements) != len(expected) { + t.Errorf("wrong num of elements. want=%d, got=%d", + len(expected), len(array.Elements)) + continue + } + + for i, expectedElem := range expected { + testIntegerObject(t, array.Elements[i], int64(expectedElem)) + } + } + } +} + +func TestArrayLiterals(t *testing.T) { + input := "[1, 2 * 2, 3 + 3]" + + evaluated := testEval(input) + result, ok := evaluated.(*object.Array) + if !ok { + t.Fatalf("object is not Array. got=%T (%+v)", evaluated, evaluated) + } + + if len(result.Elements) != 3 { + t.Fatalf("array has wrong num of elements. got=%d", + len(result.Elements)) + } + + testIntegerObject(t, result.Elements[0], 1) + testIntegerObject(t, result.Elements[1], 4) + testIntegerObject(t, result.Elements[2], 6) +} + +func TestArrayIndexExpressions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + { + "[1, 2, 3][0]", + 1, + }, + { + "[1, 2, 3][1]", + 2, + }, + { + "[1, 2, 3][2]", + 3, + }, + { + "let i = 0; [1][i];", + 1, + }, + { + "[1, 2, 3][1 + 1];", + 3, + }, + { + "let myArray = [1, 2, 3]; myArray[2];", + 3, + }, + { + "let myArray = [1, 2, 3]; myArray[0] + myArray[1] + myArray[2];", + 6, + }, + { + "let myArray = [1, 2, 3]; let i = myArray[0]; myArray[i]", + 2, + }, + { + "[1, 2, 3][3]", + nil, + }, + { + "[1, 2, 3][-1]", + nil, + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + integer, ok := tt.expected.(int) + if ok { + testIntegerObject(t, evaluated, int64(integer)) + } else { + testNullObject(t, evaluated) + } + } +} + +func TestHashLiterals(t *testing.T) { + input := `let two = "two"; + { + "one": 10 - 9, + two: 1 + 1, + "thr" + "ee": 6 / 2, + 4: 4, + true: 5, + false: 6 + }` + + evaluated := testEval(input) + result, ok := evaluated.(*object.Hash) + if !ok { + t.Fatalf("Eval didn't return Hash. got=%T (%+v)", evaluated, evaluated) + } + + expected := map[object.HashKey]int64{ + (&object.String{Value: "one"}).HashKey(): 1, + (&object.String{Value: "two"}).HashKey(): 2, + (&object.String{Value: "three"}).HashKey(): 3, + (&object.Integer{Value: 4}).HashKey(): 4, + TRUE.HashKey(): 5, + FALSE.HashKey(): 6, + } + + if len(result.Pairs) != len(expected) { + t.Fatalf("Hash has wrong num of pairs. got=%d", len(result.Pairs)) + } + + for expectedKey, expectedValue := range expected { + pair, ok := result.Pairs[expectedKey] + if !ok { + t.Errorf("no pair for given key in Pairs") + } + + testIntegerObject(t, pair.Value, expectedValue) + } +} + +func TestHashIndexExpressions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + { + `{"foo": 5}["foo"]`, + 5, + }, + { + `{"foo": 5}["bar"]`, + nil, + }, + { + `let key = "foo"; {"foo": 5}[key]`, + 5, + }, + { + `{}["foo"]`, + nil, + }, + { + `{5: 5}[5]`, + 5, + }, + { + `{true: 5}[true]`, + 5, + }, + { + `{false: 5}[false]`, + 5, + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + integer, ok := tt.expected.(int) + if ok { + testIntegerObject(t, evaluated, int64(integer)) + } else { + testNullObject(t, evaluated) + } + } +} +func testEval(input string) object.Object { + l := lexer.New(input) + p := parser.New(l) + program := p.ParseProgram() + env := object.NewEnvironment() + + return Eval(program, env) +} + +func testIntegerObject(t *testing.T, obj object.Object, expected int64) bool { + result, ok := obj.(*object.Integer) + if !ok { + t.Errorf("object is not Integer. got=%T (%+v)", obj, obj) + return false + } + if result.Value != expected { + t.Errorf("object has wrong value. got=%d, want=%d", + result.Value, expected) + return false + } + + return true +} + +func testBooleanObject(t *testing.T, obj object.Object, expected bool) bool { + result, ok := obj.(*object.Boolean) + if !ok { + t.Errorf("object is not Boolean. got=%T (%+v)", obj, obj) + return false + } + if result.Value != expected { + t.Errorf("object has wrong value. got=%t, want=%t", + result.Value, expected) + return false + } + return true +} + +func testNullObject(t *testing.T, obj object.Object) bool { + if obj != NULL { + t.Errorf("object is not NULL. got=%T (%+v)", obj, obj) + return false + } + return true +} diff --git a/wcig_code_1_2/02/src/monkey/go.mod b/wcig_code_1_2/02/src/monkey/go.mod new file mode 100644 index 0000000..3064854 --- /dev/null +++ b/wcig_code_1_2/02/src/monkey/go.mod @@ -0,0 +1,3 @@ +module monkey + +go 1.14 diff --git a/wcig_code_1_2/02/src/monkey/lexer/lexer.go b/wcig_code_1_2/02/src/monkey/lexer/lexer.go new file mode 100644 index 0000000..db4f5f7 --- /dev/null +++ b/wcig_code_1_2/02/src/monkey/lexer/lexer.go @@ -0,0 +1,157 @@ +package lexer + +import "monkey/token" + +type Lexer struct { + input string + position int // current position in input (points to current char) + readPosition int // current reading position in input (after current char) + ch byte // current char under examination +} + +func New(input string) *Lexer { + l := &Lexer{input: input} + l.readChar() + return l +} + +func (l *Lexer) NextToken() token.Token { + var tok token.Token + + l.skipWhitespace() + + switch l.ch { + case '=': + if l.peekChar() == '=' { + ch := l.ch + l.readChar() + literal := string(ch) + string(l.ch) + tok = token.Token{Type: token.EQ, Literal: literal} + } else { + tok = newToken(token.ASSIGN, l.ch) + } + case '+': + tok = newToken(token.PLUS, l.ch) + case '-': + tok = newToken(token.MINUS, l.ch) + case '!': + if l.peekChar() == '=' { + ch := l.ch + l.readChar() + literal := string(ch) + string(l.ch) + tok = token.Token{Type: token.NOT_EQ, Literal: literal} + } else { + tok = newToken(token.BANG, l.ch) + } + case '/': + tok = newToken(token.SLASH, l.ch) + case '*': + tok = newToken(token.ASTERISK, l.ch) + case '<': + tok = newToken(token.LT, l.ch) + case '>': + tok = newToken(token.GT, l.ch) + case ';': + tok = newToken(token.SEMICOLON, l.ch) + case ':': + tok = newToken(token.COLON, l.ch) + case ',': + tok = newToken(token.COMMA, l.ch) + case '{': + tok = newToken(token.LBRACE, l.ch) + case '}': + tok = newToken(token.RBRACE, l.ch) + case '(': + tok = newToken(token.LPAREN, l.ch) + case ')': + tok = newToken(token.RPAREN, l.ch) + case '"': + tok.Type = token.STRING + tok.Literal = l.readString() + case '[': + tok = newToken(token.LBRACKET, l.ch) + case ']': + tok = newToken(token.RBRACKET, l.ch) + case 0: + tok.Literal = "" + tok.Type = token.EOF + default: + if isLetter(l.ch) { + tok.Literal = l.readIdentifier() + tok.Type = token.LookupIdent(tok.Literal) + return tok + } else if isDigit(l.ch) { + tok.Type = token.INT + tok.Literal = l.readNumber() + return tok + } else { + tok = newToken(token.ILLEGAL, l.ch) + } + } + + l.readChar() + return tok +} + +func (l *Lexer) skipWhitespace() { + for l.ch == ' ' || l.ch == '\t' || l.ch == '\n' || l.ch == '\r' { + l.readChar() + } +} + +func (l *Lexer) readChar() { + if l.readPosition >= len(l.input) { + l.ch = 0 + } else { + l.ch = l.input[l.readPosition] + } + l.position = l.readPosition + l.readPosition += 1 +} + +func (l *Lexer) peekChar() byte { + if l.readPosition >= len(l.input) { + return 0 + } else { + return l.input[l.readPosition] + } +} + +func (l *Lexer) readIdentifier() string { + position := l.position + for isLetter(l.ch) { + l.readChar() + } + return l.input[position:l.position] +} + +func (l *Lexer) readNumber() string { + position := l.position + for isDigit(l.ch) { + l.readChar() + } + return l.input[position:l.position] +} + +func (l *Lexer) readString() string { + position := l.position + 1 + for { + l.readChar() + if l.ch == '"' || l.ch == 0 { + break + } + } + return l.input[position:l.position] +} + +func isLetter(ch byte) bool { + return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' +} + +func isDigit(ch byte) bool { + return '0' <= ch && ch <= '9' +} + +func newToken(tokenType token.TokenType, ch byte) token.Token { + return token.Token{Type: tokenType, Literal: string(ch)} +} diff --git a/wcig_code_1_2/02/src/monkey/lexer/lexer_test.go b/wcig_code_1_2/02/src/monkey/lexer/lexer_test.go new file mode 100644 index 0000000..e4e64a4 --- /dev/null +++ b/wcig_code_1_2/02/src/monkey/lexer/lexer_test.go @@ -0,0 +1,143 @@ +package lexer + +import ( + "testing" + + "monkey/token" +) + +func TestNextToken(t *testing.T) { + input := `let five = 5; +let ten = 10; + +let add = fn(x, y) { + x + y; +}; + +let result = add(five, ten); +!-/*5; +5 < 10 > 5; + +if (5 < 10) { + return true; +} else { + return false; +} + +10 == 10; +10 != 9; +"foobar" +"foo bar" +[1, 2]; +{"foo": "bar"} +` + + tests := []struct { + expectedType token.TokenType + expectedLiteral string + }{ + {token.LET, "let"}, + {token.IDENT, "five"}, + {token.ASSIGN, "="}, + {token.INT, "5"}, + {token.SEMICOLON, ";"}, + {token.LET, "let"}, + {token.IDENT, "ten"}, + {token.ASSIGN, "="}, + {token.INT, "10"}, + {token.SEMICOLON, ";"}, + {token.LET, "let"}, + {token.IDENT, "add"}, + {token.ASSIGN, "="}, + {token.FUNCTION, "fn"}, + {token.LPAREN, "("}, + {token.IDENT, "x"}, + {token.COMMA, ","}, + {token.IDENT, "y"}, + {token.RPAREN, ")"}, + {token.LBRACE, "{"}, + {token.IDENT, "x"}, + {token.PLUS, "+"}, + {token.IDENT, "y"}, + {token.SEMICOLON, ";"}, + {token.RBRACE, "}"}, + {token.SEMICOLON, ";"}, + {token.LET, "let"}, + {token.IDENT, "result"}, + {token.ASSIGN, "="}, + {token.IDENT, "add"}, + {token.LPAREN, "("}, + {token.IDENT, "five"}, + {token.COMMA, ","}, + {token.IDENT, "ten"}, + {token.RPAREN, ")"}, + {token.SEMICOLON, ";"}, + {token.BANG, "!"}, + {token.MINUS, "-"}, + {token.SLASH, "/"}, + {token.ASTERISK, "*"}, + {token.INT, "5"}, + {token.SEMICOLON, ";"}, + {token.INT, "5"}, + {token.LT, "<"}, + {token.INT, "10"}, + {token.GT, ">"}, + {token.INT, "5"}, + {token.SEMICOLON, ";"}, + {token.IF, "if"}, + {token.LPAREN, "("}, + {token.INT, "5"}, + {token.LT, "<"}, + {token.INT, "10"}, + {token.RPAREN, ")"}, + {token.LBRACE, "{"}, + {token.RETURN, "return"}, + {token.TRUE, "true"}, + {token.SEMICOLON, ";"}, + {token.RBRACE, "}"}, + {token.ELSE, "else"}, + {token.LBRACE, "{"}, + {token.RETURN, "return"}, + {token.FALSE, "false"}, + {token.SEMICOLON, ";"}, + {token.RBRACE, "}"}, + {token.INT, "10"}, + {token.EQ, "=="}, + {token.INT, "10"}, + {token.SEMICOLON, ";"}, + {token.INT, "10"}, + {token.NOT_EQ, "!="}, + {token.INT, "9"}, + {token.SEMICOLON, ";"}, + {token.STRING, "foobar"}, + {token.STRING, "foo bar"}, + {token.LBRACKET, "["}, + {token.INT, "1"}, + {token.COMMA, ","}, + {token.INT, "2"}, + {token.RBRACKET, "]"}, + {token.SEMICOLON, ";"}, + {token.LBRACE, "{"}, + {token.STRING, "foo"}, + {token.COLON, ":"}, + {token.STRING, "bar"}, + {token.RBRACE, "}"}, + {token.EOF, ""}, + } + + l := New(input) + + for i, tt := range tests { + tok := l.NextToken() + + if tok.Type != tt.expectedType { + t.Fatalf("tests[%d] - tokentype wrong. expected=%q, got=%q", + i, tt.expectedType, tok.Type) + } + + if tok.Literal != tt.expectedLiteral { + t.Fatalf("tests[%d] - literal wrong. expected=%q, got=%q", + i, tt.expectedLiteral, tok.Literal) + } + } +} diff --git a/wcig_code_1_2/02/src/monkey/main.go b/wcig_code_1_2/02/src/monkey/main.go new file mode 100644 index 0000000..1d27138 --- /dev/null +++ b/wcig_code_1_2/02/src/monkey/main.go @@ -0,0 +1,19 @@ +package main + +import ( + "fmt" + "monkey/repl" + "os" + "os/user" +) + +func main() { + user, err := user.Current() + if err != nil { + panic(err) + } + fmt.Printf("Hello %s! This is the Monkey programming language!\n", + user.Username) + fmt.Printf("Feel free to type in commands\n") + repl.Start(os.Stdin, os.Stdout) +} diff --git a/wcig_code_1_2/02/src/monkey/object/environment.go b/wcig_code_1_2/02/src/monkey/object/environment.go new file mode 100644 index 0000000..6f31070 --- /dev/null +++ b/wcig_code_1_2/02/src/monkey/object/environment.go @@ -0,0 +1,30 @@ +package object + +func NewEnclosedEnvironment(outer *Environment) *Environment { + env := NewEnvironment() + env.outer = outer + return env +} + +func NewEnvironment() *Environment { + s := make(map[string]Object) + return &Environment{store: s, outer: nil} +} + +type Environment struct { + store map[string]Object + outer *Environment +} + +func (e *Environment) Get(name string) (Object, bool) { + obj, ok := e.store[name] + if !ok && e.outer != nil { + obj, ok = e.outer.Get(name) + } + return obj, ok +} + +func (e *Environment) Set(name string, val Object) Object { + e.store[name] = val + return val +} diff --git a/wcig_code_1_2/02/src/monkey/object/object.go b/wcig_code_1_2/02/src/monkey/object/object.go new file mode 100644 index 0000000..2c2a1b0 --- /dev/null +++ b/wcig_code_1_2/02/src/monkey/object/object.go @@ -0,0 +1,182 @@ +package object + +import ( + "bytes" + "fmt" + "hash/fnv" + "monkey/ast" + "strings" +) + +type BuiltinFunction func(args ...Object) Object + +type ObjectType string + +const ( + NULL_OBJ = "NULL" + ERROR_OBJ = "ERROR" + + INTEGER_OBJ = "INTEGER" + BOOLEAN_OBJ = "BOOLEAN" + STRING_OBJ = "STRING" + + RETURN_VALUE_OBJ = "RETURN_VALUE" + + FUNCTION_OBJ = "FUNCTION" + BUILTIN_OBJ = "BUILTIN" + + ARRAY_OBJ = "ARRAY" + HASH_OBJ = "HASH" +) + +type HashKey struct { + Type ObjectType + Value uint64 +} + +type Hashable interface { + HashKey() HashKey +} + +type Object interface { + Type() ObjectType + Inspect() string +} + +type Integer struct { + Value int64 +} + +func (i *Integer) Type() ObjectType { return INTEGER_OBJ } +func (i *Integer) Inspect() string { return fmt.Sprintf("%d", i.Value) } +func (i *Integer) HashKey() HashKey { + return HashKey{Type: i.Type(), Value: uint64(i.Value)} +} + +type Boolean struct { + Value bool +} + +func (b *Boolean) Type() ObjectType { return BOOLEAN_OBJ } +func (b *Boolean) Inspect() string { return fmt.Sprintf("%t", b.Value) } +func (b *Boolean) HashKey() HashKey { + var value uint64 + + if b.Value { + value = 1 + } else { + value = 0 + } + + return HashKey{Type: b.Type(), Value: value} +} + +type Null struct{} + +func (n *Null) Type() ObjectType { return NULL_OBJ } +func (n *Null) Inspect() string { return "null" } + +type ReturnValue struct { + Value Object +} + +func (rv *ReturnValue) Type() ObjectType { return RETURN_VALUE_OBJ } +func (rv *ReturnValue) Inspect() string { return rv.Value.Inspect() } + +type Error struct { + Message string +} + +func (e *Error) Type() ObjectType { return ERROR_OBJ } +func (e *Error) Inspect() string { return "ERROR: " + e.Message } + +type Function struct { + Parameters []*ast.Identifier + Body *ast.BlockStatement + Env *Environment +} + +func (f *Function) Type() ObjectType { return FUNCTION_OBJ } +func (f *Function) Inspect() string { + var out bytes.Buffer + + params := []string{} + for _, p := range f.Parameters { + params = append(params, p.String()) + } + + out.WriteString("fn") + out.WriteString("(") + out.WriteString(strings.Join(params, ", ")) + out.WriteString(") {\n") + out.WriteString(f.Body.String()) + out.WriteString("\n}") + + return out.String() +} + +type String struct { + Value string +} + +func (s *String) Type() ObjectType { return STRING_OBJ } +func (s *String) Inspect() string { return s.Value } +func (s *String) HashKey() HashKey { + h := fnv.New64a() + h.Write([]byte(s.Value)) + + return HashKey{Type: s.Type(), Value: h.Sum64()} +} + +type Builtin struct { + Fn BuiltinFunction +} + +func (b *Builtin) Type() ObjectType { return BUILTIN_OBJ } +func (b *Builtin) Inspect() string { return "builtin function" } + +type Array struct { + Elements []Object +} + +func (ao *Array) Type() ObjectType { return ARRAY_OBJ } +func (ao *Array) Inspect() string { + var out bytes.Buffer + + elements := []string{} + for _, e := range ao.Elements { + elements = append(elements, e.Inspect()) + } + + out.WriteString("[") + out.WriteString(strings.Join(elements, ", ")) + out.WriteString("]") + + return out.String() +} + +type HashPair struct { + Key Object + Value Object +} + +type Hash struct { + Pairs map[HashKey]HashPair +} + +func (h *Hash) Type() ObjectType { return HASH_OBJ } +func (h *Hash) Inspect() string { + var out bytes.Buffer + + pairs := []string{} + for _, pair := range h.Pairs { + pairs = append(pairs, fmt.Sprintf("%s: %s", + pair.Key.Inspect(), pair.Value.Inspect())) + } + + out.WriteString("{") + out.WriteString(strings.Join(pairs, ", ")) + out.WriteString("}") + + return out.String() +} diff --git a/wcig_code_1_2/02/src/monkey/object/object_test.go b/wcig_code_1_2/02/src/monkey/object/object_test.go new file mode 100644 index 0000000..63228a2 --- /dev/null +++ b/wcig_code_1_2/02/src/monkey/object/object_test.go @@ -0,0 +1,60 @@ +package object + +import "testing" + +func TestStringHashKey(t *testing.T) { + hello1 := &String{Value: "Hello World"} + hello2 := &String{Value: "Hello World"} + diff1 := &String{Value: "My name is johnny"} + diff2 := &String{Value: "My name is johnny"} + + if hello1.HashKey() != hello2.HashKey() { + t.Errorf("strings with same content have different hash keys") + } + + if diff1.HashKey() != diff2.HashKey() { + t.Errorf("strings with same content have different hash keys") + } + + if hello1.HashKey() == diff1.HashKey() { + t.Errorf("strings with different content have same hash keys") + } +} + +func TestBooleanHashKey(t *testing.T) { + true1 := &Boolean{Value: true} + true2 := &Boolean{Value: true} + false1 := &Boolean{Value: false} + false2 := &Boolean{Value: false} + + if true1.HashKey() != true2.HashKey() { + t.Errorf("trues do not have same hash key") + } + + if false1.HashKey() != false2.HashKey() { + t.Errorf("falses do not have same hash key") + } + + if true1.HashKey() == false1.HashKey() { + t.Errorf("true has same hash key as false") + } +} + +func TestIntegerHashKey(t *testing.T) { + one1 := &Integer{Value: 1} + one2 := &Integer{Value: 1} + two1 := &Integer{Value: 2} + two2 := &Integer{Value: 2} + + if one1.HashKey() != one2.HashKey() { + t.Errorf("integers with same content have twoerent hash keys") + } + + if two1.HashKey() != two2.HashKey() { + t.Errorf("integers with same content have twoerent hash keys") + } + + if one1.HashKey() == two1.HashKey() { + t.Errorf("integers with twoerent content have same hash keys") + } +} diff --git a/wcig_code_1_2/02/src/monkey/parser/parser.go b/wcig_code_1_2/02/src/monkey/parser/parser.go new file mode 100644 index 0000000..0a1f19c --- /dev/null +++ b/wcig_code_1_2/02/src/monkey/parser/parser.go @@ -0,0 +1,491 @@ +package parser + +import ( + "fmt" + "monkey/ast" + "monkey/lexer" + "monkey/token" + "strconv" +) + +const ( + _ int = iota + LOWEST + EQUALS // == + LESSGREATER // > or < + SUM // + + PRODUCT // * + PREFIX // -X or !X + CALL // myFunction(X) + INDEX // array[index] +) + +var precedences = map[token.TokenType]int{ + token.EQ: EQUALS, + token.NOT_EQ: EQUALS, + token.LT: LESSGREATER, + token.GT: LESSGREATER, + token.PLUS: SUM, + token.MINUS: SUM, + token.SLASH: PRODUCT, + token.ASTERISK: PRODUCT, + token.LPAREN: CALL, + token.LBRACKET: INDEX, +} + +type ( + prefixParseFn func() ast.Expression + infixParseFn func(ast.Expression) ast.Expression +) + +type Parser struct { + l *lexer.Lexer + errors []string + + curToken token.Token + peekToken token.Token + + prefixParseFns map[token.TokenType]prefixParseFn + infixParseFns map[token.TokenType]infixParseFn +} + +func New(l *lexer.Lexer) *Parser { + p := &Parser{ + l: l, + errors: []string{}, + } + + p.prefixParseFns = make(map[token.TokenType]prefixParseFn) + p.registerPrefix(token.IDENT, p.parseIdentifier) + p.registerPrefix(token.INT, p.parseIntegerLiteral) + p.registerPrefix(token.STRING, p.parseStringLiteral) + p.registerPrefix(token.BANG, p.parsePrefixExpression) + p.registerPrefix(token.MINUS, p.parsePrefixExpression) + p.registerPrefix(token.TRUE, p.parseBoolean) + p.registerPrefix(token.FALSE, p.parseBoolean) + p.registerPrefix(token.LPAREN, p.parseGroupedExpression) + p.registerPrefix(token.IF, p.parseIfExpression) + p.registerPrefix(token.FUNCTION, p.parseFunctionLiteral) + p.registerPrefix(token.LBRACKET, p.parseArrayLiteral) + p.registerPrefix(token.LBRACE, p.parseHashLiteral) + + p.infixParseFns = make(map[token.TokenType]infixParseFn) + p.registerInfix(token.PLUS, p.parseInfixExpression) + p.registerInfix(token.MINUS, p.parseInfixExpression) + p.registerInfix(token.SLASH, p.parseInfixExpression) + p.registerInfix(token.ASTERISK, p.parseInfixExpression) + p.registerInfix(token.EQ, p.parseInfixExpression) + p.registerInfix(token.NOT_EQ, p.parseInfixExpression) + p.registerInfix(token.LT, p.parseInfixExpression) + p.registerInfix(token.GT, p.parseInfixExpression) + + p.registerInfix(token.LPAREN, p.parseCallExpression) + p.registerInfix(token.LBRACKET, p.parseIndexExpression) + + // Read two tokens, so curToken and peekToken are both set + p.nextToken() + p.nextToken() + + return p +} + +func (p *Parser) nextToken() { + p.curToken = p.peekToken + p.peekToken = p.l.NextToken() +} + +func (p *Parser) curTokenIs(t token.TokenType) bool { + return p.curToken.Type == t +} + +func (p *Parser) peekTokenIs(t token.TokenType) bool { + return p.peekToken.Type == t +} + +func (p *Parser) expectPeek(t token.TokenType) bool { + if p.peekTokenIs(t) { + p.nextToken() + return true + } else { + p.peekError(t) + return false + } +} + +func (p *Parser) Errors() []string { + return p.errors +} + +func (p *Parser) peekError(t token.TokenType) { + msg := fmt.Sprintf("expected next token to be %s, got %s instead", + t, p.peekToken.Type) + p.errors = append(p.errors, msg) +} + +func (p *Parser) noPrefixParseFnError(t token.TokenType) { + msg := fmt.Sprintf("no prefix parse function for %s found", t) + p.errors = append(p.errors, msg) +} + +func (p *Parser) ParseProgram() *ast.Program { + program := &ast.Program{} + program.Statements = []ast.Statement{} + + for !p.curTokenIs(token.EOF) { + stmt := p.parseStatement() + if stmt != nil { + program.Statements = append(program.Statements, stmt) + } + p.nextToken() + } + + return program +} + +func (p *Parser) parseStatement() ast.Statement { + switch p.curToken.Type { + case token.LET: + return p.parseLetStatement() + case token.RETURN: + return p.parseReturnStatement() + default: + return p.parseExpressionStatement() + } +} + +func (p *Parser) parseLetStatement() *ast.LetStatement { + stmt := &ast.LetStatement{Token: p.curToken} + + if !p.expectPeek(token.IDENT) { + return nil + } + + stmt.Name = &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + + if !p.expectPeek(token.ASSIGN) { + return nil + } + + p.nextToken() + + stmt.Value = p.parseExpression(LOWEST) + + if p.peekTokenIs(token.SEMICOLON) { + p.nextToken() + } + + return stmt +} + +func (p *Parser) parseReturnStatement() *ast.ReturnStatement { + stmt := &ast.ReturnStatement{Token: p.curToken} + + p.nextToken() + + stmt.ReturnValue = p.parseExpression(LOWEST) + + if p.peekTokenIs(token.SEMICOLON) { + p.nextToken() + } + + return stmt +} + +func (p *Parser) parseExpressionStatement() *ast.ExpressionStatement { + stmt := &ast.ExpressionStatement{Token: p.curToken} + + stmt.Expression = p.parseExpression(LOWEST) + + if p.peekTokenIs(token.SEMICOLON) { + p.nextToken() + } + + return stmt +} + +func (p *Parser) parseExpression(precedence int) ast.Expression { + prefix := p.prefixParseFns[p.curToken.Type] + if prefix == nil { + p.noPrefixParseFnError(p.curToken.Type) + return nil + } + leftExp := prefix() + + for !p.peekTokenIs(token.SEMICOLON) && precedence < p.peekPrecedence() { + infix := p.infixParseFns[p.peekToken.Type] + if infix == nil { + return leftExp + } + + p.nextToken() + + leftExp = infix(leftExp) + } + + return leftExp +} + +func (p *Parser) peekPrecedence() int { + if p, ok := precedences[p.peekToken.Type]; ok { + return p + } + + return LOWEST +} + +func (p *Parser) curPrecedence() int { + if p, ok := precedences[p.curToken.Type]; ok { + return p + } + + return LOWEST +} + +func (p *Parser) parseIdentifier() ast.Expression { + return &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} +} + +func (p *Parser) parseIntegerLiteral() ast.Expression { + lit := &ast.IntegerLiteral{Token: p.curToken} + + value, err := strconv.ParseInt(p.curToken.Literal, 0, 64) + if err != nil { + msg := fmt.Sprintf("could not parse %q as integer", p.curToken.Literal) + p.errors = append(p.errors, msg) + return nil + } + + lit.Value = value + + return lit +} + +func (p *Parser) parseStringLiteral() ast.Expression { + return &ast.StringLiteral{Token: p.curToken, Value: p.curToken.Literal} +} + +func (p *Parser) parsePrefixExpression() ast.Expression { + expression := &ast.PrefixExpression{ + Token: p.curToken, + Operator: p.curToken.Literal, + } + + p.nextToken() + + expression.Right = p.parseExpression(PREFIX) + + return expression +} + +func (p *Parser) parseInfixExpression(left ast.Expression) ast.Expression { + expression := &ast.InfixExpression{ + Token: p.curToken, + Operator: p.curToken.Literal, + Left: left, + } + + precedence := p.curPrecedence() + p.nextToken() + expression.Right = p.parseExpression(precedence) + + return expression +} + +func (p *Parser) parseBoolean() ast.Expression { + return &ast.Boolean{Token: p.curToken, Value: p.curTokenIs(token.TRUE)} +} + +func (p *Parser) parseGroupedExpression() ast.Expression { + p.nextToken() + + exp := p.parseExpression(LOWEST) + + if !p.expectPeek(token.RPAREN) { + return nil + } + + return exp +} + +func (p *Parser) parseIfExpression() ast.Expression { + expression := &ast.IfExpression{Token: p.curToken} + + if !p.expectPeek(token.LPAREN) { + return nil + } + + p.nextToken() + expression.Condition = p.parseExpression(LOWEST) + + if !p.expectPeek(token.RPAREN) { + return nil + } + + if !p.expectPeek(token.LBRACE) { + return nil + } + + expression.Consequence = p.parseBlockStatement() + + if p.peekTokenIs(token.ELSE) { + p.nextToken() + + if !p.expectPeek(token.LBRACE) { + return nil + } + + expression.Alternative = p.parseBlockStatement() + } + + return expression +} + +func (p *Parser) parseBlockStatement() *ast.BlockStatement { + block := &ast.BlockStatement{Token: p.curToken} + block.Statements = []ast.Statement{} + + p.nextToken() + + for !p.curTokenIs(token.RBRACE) && !p.curTokenIs(token.EOF) { + stmt := p.parseStatement() + if stmt != nil { + block.Statements = append(block.Statements, stmt) + } + p.nextToken() + } + + return block +} + +func (p *Parser) parseFunctionLiteral() ast.Expression { + lit := &ast.FunctionLiteral{Token: p.curToken} + + if !p.expectPeek(token.LPAREN) { + return nil + } + + lit.Parameters = p.parseFunctionParameters() + + if !p.expectPeek(token.LBRACE) { + return nil + } + + lit.Body = p.parseBlockStatement() + + return lit +} + +func (p *Parser) parseFunctionParameters() []*ast.Identifier { + identifiers := []*ast.Identifier{} + + if p.peekTokenIs(token.RPAREN) { + p.nextToken() + return identifiers + } + + p.nextToken() + + ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + identifiers = append(identifiers, ident) + + for p.peekTokenIs(token.COMMA) { + p.nextToken() + p.nextToken() + ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + identifiers = append(identifiers, ident) + } + + if !p.expectPeek(token.RPAREN) { + return nil + } + + return identifiers +} + +func (p *Parser) parseCallExpression(function ast.Expression) ast.Expression { + exp := &ast.CallExpression{Token: p.curToken, Function: function} + exp.Arguments = p.parseExpressionList(token.RPAREN) + return exp +} + +func (p *Parser) parseExpressionList(end token.TokenType) []ast.Expression { + list := []ast.Expression{} + + if p.peekTokenIs(end) { + p.nextToken() + return list + } + + p.nextToken() + list = append(list, p.parseExpression(LOWEST)) + + for p.peekTokenIs(token.COMMA) { + p.nextToken() + p.nextToken() + list = append(list, p.parseExpression(LOWEST)) + } + + if !p.expectPeek(end) { + return nil + } + + return list +} + +func (p *Parser) parseArrayLiteral() ast.Expression { + array := &ast.ArrayLiteral{Token: p.curToken} + + array.Elements = p.parseExpressionList(token.RBRACKET) + + return array +} + +func (p *Parser) parseIndexExpression(left ast.Expression) ast.Expression { + exp := &ast.IndexExpression{Token: p.curToken, Left: left} + + p.nextToken() + exp.Index = p.parseExpression(LOWEST) + + if !p.expectPeek(token.RBRACKET) { + return nil + } + + return exp +} + +func (p *Parser) parseHashLiteral() ast.Expression { + hash := &ast.HashLiteral{Token: p.curToken} + hash.Pairs = make(map[ast.Expression]ast.Expression) + + for !p.peekTokenIs(token.RBRACE) { + p.nextToken() + key := p.parseExpression(LOWEST) + + if !p.expectPeek(token.COLON) { + return nil + } + + p.nextToken() + value := p.parseExpression(LOWEST) + + hash.Pairs[key] = value + + if !p.peekTokenIs(token.RBRACE) && !p.expectPeek(token.COMMA) { + return nil + } + } + + if !p.expectPeek(token.RBRACE) { + return nil + } + + return hash +} + +func (p *Parser) registerPrefix(tokenType token.TokenType, fn prefixParseFn) { + p.prefixParseFns[tokenType] = fn +} + +func (p *Parser) registerInfix(tokenType token.TokenType, fn infixParseFn) { + p.infixParseFns[tokenType] = fn +} diff --git a/wcig_code_1_2/02/src/monkey/parser/parser_test.go b/wcig_code_1_2/02/src/monkey/parser/parser_test.go new file mode 100644 index 0000000..674487d --- /dev/null +++ b/wcig_code_1_2/02/src/monkey/parser/parser_test.go @@ -0,0 +1,1083 @@ +package parser + +import ( + "fmt" + "monkey/ast" + "monkey/lexer" + "testing" +) + +func TestLetStatements(t *testing.T) { + tests := []struct { + input string + expectedIdentifier string + expectedValue interface{} + }{ + {"let x = 5;", "x", 5}, + {"let y = true;", "y", true}, + {"let foobar = y;", "foobar", "y"}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain 1 statements. got=%d", + len(program.Statements)) + } + + stmt := program.Statements[0] + if !testLetStatement(t, stmt, tt.expectedIdentifier) { + return + } + + val := stmt.(*ast.LetStatement).Value + if !testLiteralExpression(t, val, tt.expectedValue) { + return + } + } +} + +func TestReturnStatements(t *testing.T) { + tests := []struct { + input string + expectedValue interface{} + }{ + {"return 5;", 5}, + {"return true;", true}, + {"return foobar;", "foobar"}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain 1 statements. got=%d", + len(program.Statements)) + } + + stmt := program.Statements[0] + returnStmt, ok := stmt.(*ast.ReturnStatement) + if !ok { + t.Fatalf("stmt not *ast.returnStatement. got=%T", stmt) + } + if returnStmt.TokenLiteral() != "return" { + t.Fatalf("returnStmt.TokenLiteral not 'return', got %q", + returnStmt.TokenLiteral()) + } + if testLiteralExpression(t, returnStmt.ReturnValue, tt.expectedValue) { + return + } + } +} + +func TestIdentifierExpression(t *testing.T) { + input := "foobar;" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program has not enough statements. got=%d", + len(program.Statements)) + } + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + ident, ok := stmt.Expression.(*ast.Identifier) + if !ok { + t.Fatalf("exp not *ast.Identifier. got=%T", stmt.Expression) + } + if ident.Value != "foobar" { + t.Errorf("ident.Value not %s. got=%s", "foobar", ident.Value) + } + if ident.TokenLiteral() != "foobar" { + t.Errorf("ident.TokenLiteral not %s. got=%s", "foobar", + ident.TokenLiteral()) + } +} + +func TestIntegerLiteralExpression(t *testing.T) { + input := "5;" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program has not enough statements. got=%d", + len(program.Statements)) + } + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + literal, ok := stmt.Expression.(*ast.IntegerLiteral) + if !ok { + t.Fatalf("exp not *ast.IntegerLiteral. got=%T", stmt.Expression) + } + if literal.Value != 5 { + t.Errorf("literal.Value not %d. got=%d", 5, literal.Value) + } + if literal.TokenLiteral() != "5" { + t.Errorf("literal.TokenLiteral not %s. got=%s", "5", + literal.TokenLiteral()) + } +} + +func TestParsingPrefixExpressions(t *testing.T) { + prefixTests := []struct { + input string + operator string + value interface{} + }{ + {"!5;", "!", 5}, + {"-15;", "-", 15}, + {"!foobar;", "!", "foobar"}, + {"-foobar;", "-", "foobar"}, + {"!true;", "!", true}, + {"!false;", "!", false}, + } + + for _, tt := range prefixTests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.PrefixExpression) + if !ok { + t.Fatalf("stmt is not ast.PrefixExpression. got=%T", stmt.Expression) + } + if exp.Operator != tt.operator { + t.Fatalf("exp.Operator is not '%s'. got=%s", + tt.operator, exp.Operator) + } + if !testLiteralExpression(t, exp.Right, tt.value) { + return + } + } +} + +func TestParsingInfixExpressions(t *testing.T) { + infixTests := []struct { + input string + leftValue interface{} + operator string + rightValue interface{} + }{ + {"5 + 5;", 5, "+", 5}, + {"5 - 5;", 5, "-", 5}, + {"5 * 5;", 5, "*", 5}, + {"5 / 5;", 5, "/", 5}, + {"5 > 5;", 5, ">", 5}, + {"5 < 5;", 5, "<", 5}, + {"5 == 5;", 5, "==", 5}, + {"5 != 5;", 5, "!=", 5}, + {"foobar + barfoo;", "foobar", "+", "barfoo"}, + {"foobar - barfoo;", "foobar", "-", "barfoo"}, + {"foobar * barfoo;", "foobar", "*", "barfoo"}, + {"foobar / barfoo;", "foobar", "/", "barfoo"}, + {"foobar > barfoo;", "foobar", ">", "barfoo"}, + {"foobar < barfoo;", "foobar", "<", "barfoo"}, + {"foobar == barfoo;", "foobar", "==", "barfoo"}, + {"foobar != barfoo;", "foobar", "!=", "barfoo"}, + {"true == true", true, "==", true}, + {"true != false", true, "!=", false}, + {"false == false", false, "==", false}, + } + + for _, tt := range infixTests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + if !testInfixExpression(t, stmt.Expression, tt.leftValue, + tt.operator, tt.rightValue) { + return + } + } +} + +func TestOperatorPrecedenceParsing(t *testing.T) { + tests := []struct { + input string + expected string + }{ + { + "-a * b", + "((-a) * b)", + }, + { + "!-a", + "(!(-a))", + }, + { + "a + b + c", + "((a + b) + c)", + }, + { + "a + b - c", + "((a + b) - c)", + }, + { + "a * b * c", + "((a * b) * c)", + }, + { + "a * b / c", + "((a * b) / c)", + }, + { + "a + b / c", + "(a + (b / c))", + }, + { + "a + b * c + d / e - f", + "(((a + (b * c)) + (d / e)) - f)", + }, + { + "3 + 4; -5 * 5", + "(3 + 4)((-5) * 5)", + }, + { + "5 > 4 == 3 < 4", + "((5 > 4) == (3 < 4))", + }, + { + "5 < 4 != 3 > 4", + "((5 < 4) != (3 > 4))", + }, + { + "3 + 4 * 5 == 3 * 1 + 4 * 5", + "((3 + (4 * 5)) == ((3 * 1) + (4 * 5)))", + }, + { + "true", + "true", + }, + { + "false", + "false", + }, + { + "3 > 5 == false", + "((3 > 5) == false)", + }, + { + "3 < 5 == true", + "((3 < 5) == true)", + }, + { + "1 + (2 + 3) + 4", + "((1 + (2 + 3)) + 4)", + }, + { + "(5 + 5) * 2", + "((5 + 5) * 2)", + }, + { + "2 / (5 + 5)", + "(2 / (5 + 5))", + }, + { + "(5 + 5) * 2 * (5 + 5)", + "(((5 + 5) * 2) * (5 + 5))", + }, + { + "-(5 + 5)", + "(-(5 + 5))", + }, + { + "!(true == true)", + "(!(true == true))", + }, + { + "a + add(b * c) + d", + "((a + add((b * c))) + d)", + }, + { + "add(a, b, 1, 2 * 3, 4 + 5, add(6, 7 * 8))", + "add(a, b, 1, (2 * 3), (4 + 5), add(6, (7 * 8)))", + }, + { + "add(a + b + c * d / f + g)", + "add((((a + b) + ((c * d) / f)) + g))", + }, + { + "a * [1, 2, 3, 4][b * c] * d", + "((a * ([1, 2, 3, 4][(b * c)])) * d)", + }, + { + "add(a * b[2], b[1], 2 * [1, 2][1])", + "add((a * (b[2])), (b[1]), (2 * ([1, 2][1])))", + }, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + actual := program.String() + if actual != tt.expected { + t.Errorf("expected=%q, got=%q", tt.expected, actual) + } + } +} + +func TestBooleanExpression(t *testing.T) { + tests := []struct { + input string + expectedBoolean bool + }{ + {"true;", true}, + {"false;", false}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program has not enough statements. got=%d", + len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + boolean, ok := stmt.Expression.(*ast.Boolean) + if !ok { + t.Fatalf("exp not *ast.Boolean. got=%T", stmt.Expression) + } + if boolean.Value != tt.expectedBoolean { + t.Errorf("boolean.Value not %t. got=%t", tt.expectedBoolean, + boolean.Value) + } + } +} + +func TestIfExpression(t *testing.T) { + input := `if (x < y) { x }` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.IfExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.IfExpression. got=%T", + stmt.Expression) + } + + if !testInfixExpression(t, exp.Condition, "x", "<", "y") { + return + } + + if len(exp.Consequence.Statements) != 1 { + t.Errorf("consequence is not 1 statements. got=%d\n", + len(exp.Consequence.Statements)) + } + + consequence, ok := exp.Consequence.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", + exp.Consequence.Statements[0]) + } + + if !testIdentifier(t, consequence.Expression, "x") { + return + } + + if exp.Alternative != nil { + t.Errorf("exp.Alternative.Statements was not nil. got=%+v", exp.Alternative) + } +} + +func TestIfElseExpression(t *testing.T) { + input := `if (x < y) { x } else { y }` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.IfExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.IfExpression. got=%T", stmt.Expression) + } + + if !testInfixExpression(t, exp.Condition, "x", "<", "y") { + return + } + + if len(exp.Consequence.Statements) != 1 { + t.Errorf("consequence is not 1 statements. got=%d\n", + len(exp.Consequence.Statements)) + } + + consequence, ok := exp.Consequence.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", + exp.Consequence.Statements[0]) + } + + if !testIdentifier(t, consequence.Expression, "x") { + return + } + + if len(exp.Alternative.Statements) != 1 { + t.Errorf("exp.Alternative.Statements does not contain 1 statements. got=%d\n", + len(exp.Alternative.Statements)) + } + + alternative, ok := exp.Alternative.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", + exp.Alternative.Statements[0]) + } + + if !testIdentifier(t, alternative.Expression, "y") { + return + } +} + +func TestFunctionLiteralParsing(t *testing.T) { + input := `fn(x, y) { x + y; }` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + function, ok := stmt.Expression.(*ast.FunctionLiteral) + if !ok { + t.Fatalf("stmt.Expression is not ast.FunctionLiteral. got=%T", + stmt.Expression) + } + + if len(function.Parameters) != 2 { + t.Fatalf("function literal parameters wrong. want 2, got=%d\n", + len(function.Parameters)) + } + + testLiteralExpression(t, function.Parameters[0], "x") + testLiteralExpression(t, function.Parameters[1], "y") + + if len(function.Body.Statements) != 1 { + t.Fatalf("function.Body.Statements has not 1 statements. got=%d\n", + len(function.Body.Statements)) + } + + bodyStmt, ok := function.Body.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("function body stmt is not ast.ExpressionStatement. got=%T", + function.Body.Statements[0]) + } + + testInfixExpression(t, bodyStmt.Expression, "x", "+", "y") +} + +func TestFunctionParameterParsing(t *testing.T) { + tests := []struct { + input string + expectedParams []string + }{ + {input: "fn() {};", expectedParams: []string{}}, + {input: "fn(x) {};", expectedParams: []string{"x"}}, + {input: "fn(x, y, z) {};", expectedParams: []string{"x", "y", "z"}}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + function := stmt.Expression.(*ast.FunctionLiteral) + + if len(function.Parameters) != len(tt.expectedParams) { + t.Errorf("length parameters wrong. want %d, got=%d\n", + len(tt.expectedParams), len(function.Parameters)) + } + + for i, ident := range tt.expectedParams { + testLiteralExpression(t, function.Parameters[i], ident) + } + } +} + +func TestCallExpressionParsing(t *testing.T) { + input := "add(1, 2 * 3, 4 + 5);" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("stmt is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.CallExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.CallExpression. got=%T", + stmt.Expression) + } + + if !testIdentifier(t, exp.Function, "add") { + return + } + + if len(exp.Arguments) != 3 { + t.Fatalf("wrong length of arguments. got=%d", len(exp.Arguments)) + } + + testLiteralExpression(t, exp.Arguments[0], 1) + testInfixExpression(t, exp.Arguments[1], 2, "*", 3) + testInfixExpression(t, exp.Arguments[2], 4, "+", 5) +} + +func TestCallExpressionParameterParsing(t *testing.T) { + tests := []struct { + input string + expectedIdent string + expectedArgs []string + }{ + { + input: "add();", + expectedIdent: "add", + expectedArgs: []string{}, + }, + { + input: "add(1);", + expectedIdent: "add", + expectedArgs: []string{"1"}, + }, + { + input: "add(1, 2 * 3, 4 + 5);", + expectedIdent: "add", + expectedArgs: []string{"1", "(2 * 3)", "(4 + 5)"}, + }, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + exp, ok := stmt.Expression.(*ast.CallExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.CallExpression. got=%T", + stmt.Expression) + } + + if !testIdentifier(t, exp.Function, tt.expectedIdent) { + return + } + + if len(exp.Arguments) != len(tt.expectedArgs) { + t.Fatalf("wrong number of arguments. want=%d, got=%d", + len(tt.expectedArgs), len(exp.Arguments)) + } + + for i, arg := range tt.expectedArgs { + if exp.Arguments[i].String() != arg { + t.Errorf("argument %d wrong. want=%q, got=%q", i, + arg, exp.Arguments[i].String()) + } + } + } +} + +func TestStringLiteralExpression(t *testing.T) { + input := `"hello world";` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + literal, ok := stmt.Expression.(*ast.StringLiteral) + if !ok { + t.Fatalf("exp not *ast.StringLiteral. got=%T", stmt.Expression) + } + + if literal.Value != "hello world" { + t.Errorf("literal.Value not %q. got=%q", "hello world", literal.Value) + } +} + +func TestParsingEmptyArrayLiterals(t *testing.T) { + input := "[]" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + array, ok := stmt.Expression.(*ast.ArrayLiteral) + if !ok { + t.Fatalf("exp not ast.ArrayLiteral. got=%T", stmt.Expression) + } + + if len(array.Elements) != 0 { + t.Errorf("len(array.Elements) not 0. got=%d", len(array.Elements)) + } +} + +func TestParsingArrayLiterals(t *testing.T) { + input := "[1, 2 * 2, 3 + 3]" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + array, ok := stmt.Expression.(*ast.ArrayLiteral) + if !ok { + t.Fatalf("exp not ast.ArrayLiteral. got=%T", stmt.Expression) + } + + if len(array.Elements) != 3 { + t.Fatalf("len(array.Elements) not 3. got=%d", len(array.Elements)) + } + + testIntegerLiteral(t, array.Elements[0], 1) + testInfixExpression(t, array.Elements[1], 2, "*", 2) + testInfixExpression(t, array.Elements[2], 3, "+", 3) +} + +func TestParsingIndexExpressions(t *testing.T) { + input := "myArray[1 + 1]" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + indexExp, ok := stmt.Expression.(*ast.IndexExpression) + if !ok { + t.Fatalf("exp not *ast.IndexExpression. got=%T", stmt.Expression) + } + + if !testIdentifier(t, indexExp.Left, "myArray") { + return + } + + if !testInfixExpression(t, indexExp.Index, 1, "+", 1) { + return + } +} + +func TestParsingEmptyHashLiteral(t *testing.T) { + input := "{}" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + if len(hash.Pairs) != 0 { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } +} + +func TestParsingHashLiteralsStringKeys(t *testing.T) { + input := `{"one": 1, "two": 2, "three": 3}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + expected := map[string]int64{ + "one": 1, + "two": 2, + "three": 3, + } + + if len(hash.Pairs) != len(expected) { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } + + for key, value := range hash.Pairs { + literal, ok := key.(*ast.StringLiteral) + if !ok { + t.Errorf("key is not ast.StringLiteral. got=%T", key) + continue + } + + expectedValue := expected[literal.String()] + testIntegerLiteral(t, value, expectedValue) + } +} + +func TestParsingHashLiteralsBooleanKeys(t *testing.T) { + input := `{true: 1, false: 2}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + expected := map[string]int64{ + "true": 1, + "false": 2, + } + + if len(hash.Pairs) != len(expected) { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } + + for key, value := range hash.Pairs { + boolean, ok := key.(*ast.Boolean) + if !ok { + t.Errorf("key is not ast.BooleanLiteral. got=%T", key) + continue + } + + expectedValue := expected[boolean.String()] + testIntegerLiteral(t, value, expectedValue) + } +} + +func TestParsingHashLiteralsIntegerKeys(t *testing.T) { + input := `{1: 1, 2: 2, 3: 3}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + expected := map[string]int64{ + "1": 1, + "2": 2, + "3": 3, + } + + if len(hash.Pairs) != len(expected) { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } + + for key, value := range hash.Pairs { + integer, ok := key.(*ast.IntegerLiteral) + if !ok { + t.Errorf("key is not ast.IntegerLiteral. got=%T", key) + continue + } + + expectedValue := expected[integer.String()] + + testIntegerLiteral(t, value, expectedValue) + } +} + +func TestParsingHashLiteralsWithExpressions(t *testing.T) { + input := `{"one": 0 + 1, "two": 10 - 8, "three": 15 / 5}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + if len(hash.Pairs) != 3 { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } + + tests := map[string]func(ast.Expression){ + "one": func(e ast.Expression) { + testInfixExpression(t, e, 0, "+", 1) + }, + "two": func(e ast.Expression) { + testInfixExpression(t, e, 10, "-", 8) + }, + "three": func(e ast.Expression) { + testInfixExpression(t, e, 15, "/", 5) + }, + } + + for key, value := range hash.Pairs { + literal, ok := key.(*ast.StringLiteral) + if !ok { + t.Errorf("key is not ast.StringLiteral. got=%T", key) + continue + } + + testFunc, ok := tests[literal.String()] + if !ok { + t.Errorf("No test function for key %q found", literal.String()) + continue + } + + testFunc(value) + } +} + +func testLetStatement(t *testing.T, s ast.Statement, name string) bool { + if s.TokenLiteral() != "let" { + t.Errorf("s.TokenLiteral not 'let'. got=%q", s.TokenLiteral()) + return false + } + + letStmt, ok := s.(*ast.LetStatement) + if !ok { + t.Errorf("s not *ast.LetStatement. got=%T", s) + return false + } + + if letStmt.Name.Value != name { + t.Errorf("letStmt.Name.Value not '%s'. got=%s", name, letStmt.Name.Value) + return false + } + + if letStmt.Name.TokenLiteral() != name { + t.Errorf("letStmt.Name.TokenLiteral() not '%s'. got=%s", + name, letStmt.Name.TokenLiteral()) + return false + } + + return true +} + +func testInfixExpression(t *testing.T, exp ast.Expression, left interface{}, + operator string, right interface{}) bool { + + opExp, ok := exp.(*ast.InfixExpression) + if !ok { + t.Errorf("exp is not ast.InfixExpression. got=%T(%s)", exp, exp) + return false + } + + if !testLiteralExpression(t, opExp.Left, left) { + return false + } + + if opExp.Operator != operator { + t.Errorf("exp.Operator is not '%s'. got=%q", operator, opExp.Operator) + return false + } + + if !testLiteralExpression(t, opExp.Right, right) { + return false + } + + return true +} + +func testLiteralExpression( + t *testing.T, + exp ast.Expression, + expected interface{}, +) bool { + switch v := expected.(type) { + case int: + return testIntegerLiteral(t, exp, int64(v)) + case int64: + return testIntegerLiteral(t, exp, v) + case string: + return testIdentifier(t, exp, v) + case bool: + return testBooleanLiteral(t, exp, v) + } + t.Errorf("type of exp not handled. got=%T", exp) + return false +} + +func testIntegerLiteral(t *testing.T, il ast.Expression, value int64) bool { + integ, ok := il.(*ast.IntegerLiteral) + if !ok { + t.Errorf("il not *ast.IntegerLiteral. got=%T", il) + return false + } + + if integ.Value != value { + t.Errorf("integ.Value not %d. got=%d", value, integ.Value) + return false + } + + if integ.TokenLiteral() != fmt.Sprintf("%d", value) { + t.Errorf("integ.TokenLiteral not %d. got=%s", value, + integ.TokenLiteral()) + return false + } + + return true +} + +func testIdentifier(t *testing.T, exp ast.Expression, value string) bool { + ident, ok := exp.(*ast.Identifier) + if !ok { + t.Errorf("exp not *ast.Identifier. got=%T", exp) + return false + } + + if ident.Value != value { + t.Errorf("ident.Value not %s. got=%s", value, ident.Value) + return false + } + + if ident.TokenLiteral() != value { + t.Errorf("ident.TokenLiteral not %s. got=%s", value, + ident.TokenLiteral()) + return false + } + + return true +} + +func testBooleanLiteral(t *testing.T, exp ast.Expression, value bool) bool { + bo, ok := exp.(*ast.Boolean) + if !ok { + t.Errorf("exp not *ast.Boolean. got=%T", exp) + return false + } + + if bo.Value != value { + t.Errorf("bo.Value not %t. got=%t", value, bo.Value) + return false + } + + if bo.TokenLiteral() != fmt.Sprintf("%t", value) { + t.Errorf("bo.TokenLiteral not %t. got=%s", + value, bo.TokenLiteral()) + return false + } + + return true +} + +func checkParserErrors(t *testing.T, p *Parser) { + errors := p.Errors() + if len(errors) == 0 { + return + } + + t.Errorf("parser has %d errors", len(errors)) + for _, msg := range errors { + t.Errorf("parser error: %q", msg) + } + t.FailNow() +} diff --git a/wcig_code_1_2/02/src/monkey/parser/parser_tracing.go b/wcig_code_1_2/02/src/monkey/parser/parser_tracing.go new file mode 100644 index 0000000..5fc569b --- /dev/null +++ b/wcig_code_1_2/02/src/monkey/parser/parser_tracing.go @@ -0,0 +1,32 @@ +package parser + +import ( + "fmt" + "strings" +) + +var traceLevel int = 0 + +const traceIdentPlaceholder string = "\t" + +func identLevel() string { + return strings.Repeat(traceIdentPlaceholder, traceLevel-1) +} + +func tracePrint(fs string) { + fmt.Printf("%s%s\n", identLevel(), fs) +} + +func incIdent() { traceLevel = traceLevel + 1 } +func decIdent() { traceLevel = traceLevel - 1 } + +func trace(msg string) string { + incIdent() + tracePrint("BEGIN " + msg) + return msg +} + +func untrace(msg string) { + tracePrint("END " + msg) + decIdent() +} diff --git a/wcig_code_1_2/02/src/monkey/repl/repl.go b/wcig_code_1_2/02/src/monkey/repl/repl.go new file mode 100644 index 0000000..968733f --- /dev/null +++ b/wcig_code_1_2/02/src/monkey/repl/repl.go @@ -0,0 +1,75 @@ +package repl + +import ( + "bufio" + "fmt" + "io" + "monkey/compiler" + "monkey/lexer" + "monkey/parser" + "monkey/vm" +) + +const PROMPT = ">> " + +func Start(in io.Reader, out io.Writer) { + scanner := bufio.NewScanner(in) + + for { + fmt.Fprintf(out, PROMPT) + scanned := scanner.Scan() + if !scanned { + return + } + + line := scanner.Text() + l := lexer.New(line) + p := parser.New(l) + + program := p.ParseProgram() + if len(p.Errors()) != 0 { + printParserErrors(out, p.Errors()) + continue + } + + comp := compiler.New() + err := comp.Compile(program) + if err != nil { + fmt.Fprintf(out, "Woops! Compilation failed:\n %s\n", err) + continue + } + + machine := vm.New(comp.Bytecode()) + err = machine.Run() + if err != nil { + fmt.Fprintf(out, "Woops! Executing bytecode failed:\n %s\n", err) + continue + } + + stackTop := machine.StackTop() + io.WriteString(out, stackTop.Inspect()) + io.WriteString(out, "\n") + } +} + +const MONKEY_FACE = ` __,__ + .--. .-" "-. .--. + / .. \/ .-. .-. \/ .. \ + | | '| / Y \ |' | | + | \ \ \ 0 | 0 / / / | + \ '- ,\.-"""""""-./, -' / + ''-' /_ ^ ^ _\ '-'' + | \._ _./ | + \ \ '~' / / + '._ '-=-' _.' + '-----' +` + +func printParserErrors(out io.Writer, errors []string) { + io.WriteString(out, MONKEY_FACE) + io.WriteString(out, "Woops! We ran into some monkey business here!\n") + io.WriteString(out, " parser errors:\n") + for _, msg := range errors { + io.WriteString(out, "\t"+msg+"\n") + } +} diff --git a/wcig_code_1_2/02/src/monkey/token/token.go b/wcig_code_1_2/02/src/monkey/token/token.go new file mode 100644 index 0000000..3d2d2f7 --- /dev/null +++ b/wcig_code_1_2/02/src/monkey/token/token.go @@ -0,0 +1,70 @@ +package token + +type TokenType string + +const ( + ILLEGAL = "ILLEGAL" + EOF = "EOF" + + // Identifiers + literals + IDENT = "IDENT" // add, foobar, x, y, ... + INT = "INT" // 1343456 + STRING = "STRING" // "foobar" + + // Operators + ASSIGN = "=" + PLUS = "+" + MINUS = "-" + BANG = "!" + ASTERISK = "*" + SLASH = "/" + + LT = "<" + GT = ">" + + EQ = "==" + NOT_EQ = "!=" + + // Delimiters + COMMA = "," + SEMICOLON = ";" + COLON = ":" + + LPAREN = "(" + RPAREN = ")" + LBRACE = "{" + RBRACE = "}" + LBRACKET = "[" + RBRACKET = "]" + + // Keywords + FUNCTION = "FUNCTION" + LET = "LET" + TRUE = "TRUE" + FALSE = "FALSE" + IF = "IF" + ELSE = "ELSE" + RETURN = "RETURN" +) + +type Token struct { + Type TokenType + Literal string +} + +var keywords = map[string]TokenType{ + "fn": FUNCTION, + "let": LET, + "true": TRUE, + "false": FALSE, + "if": IF, + "else": ELSE, + "return": RETURN, +} + +func LookupIdent(ident string) TokenType { + if tok, ok := keywords[ident]; ok { + return tok + } + return IDENT +} diff --git a/wcig_code_1_2/02/src/monkey/vm/vm.go b/wcig_code_1_2/02/src/monkey/vm/vm.go new file mode 100644 index 0000000..fe27012 --- /dev/null +++ b/wcig_code_1_2/02/src/monkey/vm/vm.go @@ -0,0 +1,81 @@ +package vm + +import ( + "fmt" + "monkey/code" + "monkey/compiler" + "monkey/object" +) + +const StackSize = 2048 + +type VM struct { + constants []object.Object + instructions code.Instructions + + stack []object.Object + sp int // Always points to the next value. Top of stack is stack[sp-1] +} + +func New(bytecode *compiler.Bytecode) *VM { + return &VM{ + instructions: bytecode.Instructions, + constants: bytecode.Constants, + + stack: make([]object.Object, StackSize), + sp: 0, + } +} + +func (vm *VM) StackTop() object.Object { + if vm.sp == 0 { + return nil + } + return vm.stack[vm.sp-1] +} + +func (vm *VM) Run() error { + for ip := 0; ip < len(vm.instructions); ip++ { + op := code.Opcode(vm.instructions[ip]) + + switch op { + case code.OpConstant: + constIndex := code.ReadUint16(vm.instructions[ip+1:]) + ip += 2 + + err := vm.push(vm.constants[constIndex]) + if err != nil { + return err + } + + case code.OpAdd: + right := vm.pop() + left := vm.pop() + leftValue := left.(*object.Integer).Value + rightValue := right.(*object.Integer).Value + + result := leftValue + rightValue + vm.push(&object.Integer{Value: result}) + + } + } + + return nil +} + +func (vm *VM) push(o object.Object) error { + if vm.sp >= StackSize { + return fmt.Errorf("stack overflow") + } + + vm.stack[vm.sp] = o + vm.sp++ + + return nil +} + +func (vm *VM) pop() object.Object { + o := vm.stack[vm.sp-1] + vm.sp-- + return o +} diff --git a/wcig_code_1_2/02/src/monkey/vm/vm_test.go b/wcig_code_1_2/02/src/monkey/vm/vm_test.go new file mode 100644 index 0000000..d93c4aa --- /dev/null +++ b/wcig_code_1_2/02/src/monkey/vm/vm_test.go @@ -0,0 +1,87 @@ +package vm + +import ( + "fmt" + "monkey/ast" + "monkey/compiler" + "monkey/lexer" + "monkey/object" + "monkey/parser" + "testing" +) + +func TestIntegerArithmetic(t *testing.T) { + tests := []vmTestCase{ + {"1", 1}, + {"2", 2}, + {"1 + 2", 3}, + } + + runVmTests(t, tests) +} + +type vmTestCase struct { + input string + expected interface{} +} + +func runVmTests(t *testing.T, tests []vmTestCase) { + t.Helper() + + for _, tt := range tests { + program := parse(tt.input) + + comp := compiler.New() + err := comp.Compile(program) + if err != nil { + t.Fatalf("compiler error: %s", err) + } + + vm := New(comp.Bytecode()) + err = vm.Run() + if err != nil { + t.Fatalf("vm error: %s", err) + } + + stackElem := vm.StackTop() + + testExpectedObject(t, tt.expected, stackElem) + } +} + +func parse(input string) *ast.Program { + l := lexer.New(input) + p := parser.New(l) + return p.ParseProgram() +} + +func testExpectedObject( + t *testing.T, + expected interface{}, + actual object.Object, +) { + t.Helper() + + switch expected := expected.(type) { + case int: + err := testIntegerObject(int64(expected), actual) + if err != nil { + t.Errorf("testIntegerObject failed: %s", err) + } + } +} + +func testIntegerObject(expected int64, actual object.Object) error { + result, ok := actual.(*object.Integer) + if !ok { + return fmt.Errorf("object is not Integer. got=%T (%+v)", + actual, actual) + } + + if result.Value != expected { + return fmt.Errorf("object has wrong value. got=%d, want=%d", + result.Value, expected) + } + + return nil +} diff --git a/wcig_code_1_2/03/.envrc b/wcig_code_1_2/03/.envrc new file mode 100644 index 0000000..fb65ef4 --- /dev/null +++ b/wcig_code_1_2/03/.envrc @@ -0,0 +1 @@ +export GOPATH=$(pwd) diff --git a/wcig_code_1_2/03/src/monkey/ast/ast.go b/wcig_code_1_2/03/src/monkey/ast/ast.go new file mode 100644 index 0000000..fb30b05 --- /dev/null +++ b/wcig_code_1_2/03/src/monkey/ast/ast.go @@ -0,0 +1,339 @@ +package ast + +import ( + "bytes" + "monkey/token" + "strings" +) + +// The base Node interface +type Node interface { + TokenLiteral() string + String() string +} + +// All statement nodes implement this +type Statement interface { + Node + statementNode() +} + +// All expression nodes implement this +type Expression interface { + Node + expressionNode() +} + +type Program struct { + Statements []Statement +} + +func (p *Program) TokenLiteral() string { + if len(p.Statements) > 0 { + return p.Statements[0].TokenLiteral() + } else { + return "" + } +} + +func (p *Program) String() string { + var out bytes.Buffer + + for _, s := range p.Statements { + out.WriteString(s.String()) + } + + return out.String() +} + +// Statements +type LetStatement struct { + Token token.Token // the token.LET token + Name *Identifier + Value Expression +} + +func (ls *LetStatement) statementNode() {} +func (ls *LetStatement) TokenLiteral() string { return ls.Token.Literal } +func (ls *LetStatement) String() string { + var out bytes.Buffer + + out.WriteString(ls.TokenLiteral() + " ") + out.WriteString(ls.Name.String()) + out.WriteString(" = ") + + if ls.Value != nil { + out.WriteString(ls.Value.String()) + } + + out.WriteString(";") + + return out.String() +} + +type ReturnStatement struct { + Token token.Token // the 'return' token + ReturnValue Expression +} + +func (rs *ReturnStatement) statementNode() {} +func (rs *ReturnStatement) TokenLiteral() string { return rs.Token.Literal } +func (rs *ReturnStatement) String() string { + var out bytes.Buffer + + out.WriteString(rs.TokenLiteral() + " ") + + if rs.ReturnValue != nil { + out.WriteString(rs.ReturnValue.String()) + } + + out.WriteString(";") + + return out.String() +} + +type ExpressionStatement struct { + Token token.Token // the first token of the expression + Expression Expression +} + +func (es *ExpressionStatement) statementNode() {} +func (es *ExpressionStatement) TokenLiteral() string { return es.Token.Literal } +func (es *ExpressionStatement) String() string { + if es.Expression != nil { + return es.Expression.String() + } + return "" +} + +type BlockStatement struct { + Token token.Token // the { token + Statements []Statement +} + +func (bs *BlockStatement) statementNode() {} +func (bs *BlockStatement) TokenLiteral() string { return bs.Token.Literal } +func (bs *BlockStatement) String() string { + var out bytes.Buffer + + for _, s := range bs.Statements { + out.WriteString(s.String()) + } + + return out.String() +} + +// Expressions +type Identifier struct { + Token token.Token // the token.IDENT token + Value string +} + +func (i *Identifier) expressionNode() {} +func (i *Identifier) TokenLiteral() string { return i.Token.Literal } +func (i *Identifier) String() string { return i.Value } + +type Boolean struct { + Token token.Token + Value bool +} + +func (b *Boolean) expressionNode() {} +func (b *Boolean) TokenLiteral() string { return b.Token.Literal } +func (b *Boolean) String() string { return b.Token.Literal } + +type IntegerLiteral struct { + Token token.Token + Value int64 +} + +func (il *IntegerLiteral) expressionNode() {} +func (il *IntegerLiteral) TokenLiteral() string { return il.Token.Literal } +func (il *IntegerLiteral) String() string { return il.Token.Literal } + +type PrefixExpression struct { + Token token.Token // The prefix token, e.g. ! + Operator string + Right Expression +} + +func (pe *PrefixExpression) expressionNode() {} +func (pe *PrefixExpression) TokenLiteral() string { return pe.Token.Literal } +func (pe *PrefixExpression) String() string { + var out bytes.Buffer + + out.WriteString("(") + out.WriteString(pe.Operator) + out.WriteString(pe.Right.String()) + out.WriteString(")") + + return out.String() +} + +type InfixExpression struct { + Token token.Token // The operator token, e.g. + + Left Expression + Operator string + Right Expression +} + +func (oe *InfixExpression) expressionNode() {} +func (oe *InfixExpression) TokenLiteral() string { return oe.Token.Literal } +func (oe *InfixExpression) String() string { + var out bytes.Buffer + + out.WriteString("(") + out.WriteString(oe.Left.String()) + out.WriteString(" " + oe.Operator + " ") + out.WriteString(oe.Right.String()) + out.WriteString(")") + + return out.String() +} + +type IfExpression struct { + Token token.Token // The 'if' token + Condition Expression + Consequence *BlockStatement + Alternative *BlockStatement +} + +func (ie *IfExpression) expressionNode() {} +func (ie *IfExpression) TokenLiteral() string { return ie.Token.Literal } +func (ie *IfExpression) String() string { + var out bytes.Buffer + + out.WriteString("if") + out.WriteString(ie.Condition.String()) + out.WriteString(" ") + out.WriteString(ie.Consequence.String()) + + if ie.Alternative != nil { + out.WriteString("else ") + out.WriteString(ie.Alternative.String()) + } + + return out.String() +} + +type FunctionLiteral struct { + Token token.Token // The 'fn' token + Parameters []*Identifier + Body *BlockStatement +} + +func (fl *FunctionLiteral) expressionNode() {} +func (fl *FunctionLiteral) TokenLiteral() string { return fl.Token.Literal } +func (fl *FunctionLiteral) String() string { + var out bytes.Buffer + + params := []string{} + for _, p := range fl.Parameters { + params = append(params, p.String()) + } + + out.WriteString(fl.TokenLiteral()) + out.WriteString("(") + out.WriteString(strings.Join(params, ", ")) + out.WriteString(") ") + out.WriteString(fl.Body.String()) + + return out.String() +} + +type CallExpression struct { + Token token.Token // The '(' token + Function Expression // Identifier or FunctionLiteral + Arguments []Expression +} + +func (ce *CallExpression) expressionNode() {} +func (ce *CallExpression) TokenLiteral() string { return ce.Token.Literal } +func (ce *CallExpression) String() string { + var out bytes.Buffer + + args := []string{} + for _, a := range ce.Arguments { + args = append(args, a.String()) + } + + out.WriteString(ce.Function.String()) + out.WriteString("(") + out.WriteString(strings.Join(args, ", ")) + out.WriteString(")") + + return out.String() +} + +type StringLiteral struct { + Token token.Token + Value string +} + +func (sl *StringLiteral) expressionNode() {} +func (sl *StringLiteral) TokenLiteral() string { return sl.Token.Literal } +func (sl *StringLiteral) String() string { return sl.Token.Literal } + +type ArrayLiteral struct { + Token token.Token // the '[' token + Elements []Expression +} + +func (al *ArrayLiteral) expressionNode() {} +func (al *ArrayLiteral) TokenLiteral() string { return al.Token.Literal } +func (al *ArrayLiteral) String() string { + var out bytes.Buffer + + elements := []string{} + for _, el := range al.Elements { + elements = append(elements, el.String()) + } + + out.WriteString("[") + out.WriteString(strings.Join(elements, ", ")) + out.WriteString("]") + + return out.String() +} + +type IndexExpression struct { + Token token.Token // The [ token + Left Expression + Index Expression +} + +func (ie *IndexExpression) expressionNode() {} +func (ie *IndexExpression) TokenLiteral() string { return ie.Token.Literal } +func (ie *IndexExpression) String() string { + var out bytes.Buffer + + out.WriteString("(") + out.WriteString(ie.Left.String()) + out.WriteString("[") + out.WriteString(ie.Index.String()) + out.WriteString("])") + + return out.String() +} + +type HashLiteral struct { + Token token.Token // the '{' token + Pairs map[Expression]Expression +} + +func (hl *HashLiteral) expressionNode() {} +func (hl *HashLiteral) TokenLiteral() string { return hl.Token.Literal } +func (hl *HashLiteral) String() string { + var out bytes.Buffer + + pairs := []string{} + for key, value := range hl.Pairs { + pairs = append(pairs, key.String()+":"+value.String()) + } + + out.WriteString("{") + out.WriteString(strings.Join(pairs, ", ")) + out.WriteString("}") + + return out.String() +} diff --git a/wcig_code_1_2/03/src/monkey/ast/ast_test.go b/wcig_code_1_2/03/src/monkey/ast/ast_test.go new file mode 100644 index 0000000..14a49dc --- /dev/null +++ b/wcig_code_1_2/03/src/monkey/ast/ast_test.go @@ -0,0 +1,28 @@ +package ast + +import ( + "monkey/token" + "testing" +) + +func TestString(t *testing.T) { + program := &Program{ + Statements: []Statement{ + &LetStatement{ + Token: token.Token{Type: token.LET, Literal: "let"}, + Name: &Identifier{ + Token: token.Token{Type: token.IDENT, Literal: "myVar"}, + Value: "myVar", + }, + Value: &Identifier{ + Token: token.Token{Type: token.IDENT, Literal: "anotherVar"}, + Value: "anotherVar", + }, + }, + }, + } + + if program.String() != "let myVar = anotherVar;" { + t.Errorf("program.String() wrong. got=%q", program.String()) + } +} diff --git a/wcig_code_1_2/03/src/monkey/code/code.go b/wcig_code_1_2/03/src/monkey/code/code.go new file mode 100644 index 0000000..13771cd --- /dev/null +++ b/wcig_code_1_2/03/src/monkey/code/code.go @@ -0,0 +1,155 @@ +package code + +import ( + "bytes" + "encoding/binary" + "fmt" +) + +type Instructions []byte + +func (ins Instructions) String() string { + var out bytes.Buffer + + i := 0 + for i < len(ins) { + def, err := Lookup(ins[i]) + if err != nil { + fmt.Fprintf(&out, "ERROR: %s\n", err) + continue + } + + operands, read := ReadOperands(def, ins[i+1:]) + + fmt.Fprintf(&out, "%04d %s\n", i, ins.fmtInstruction(def, operands)) + + i += 1 + read + } + + return out.String() +} + +func (ins Instructions) fmtInstruction(def *Definition, operands []int) string { + operandCount := len(def.OperandWidths) + + if len(operands) != operandCount { + return fmt.Sprintf("ERROR: operand len %d does not match defined %d\n", + len(operands), operandCount) + } + + switch operandCount { + case 0: + return def.Name + case 1: + return fmt.Sprintf("%s %d", def.Name, operands[0]) + } + + return fmt.Sprintf("ERROR: unhandled operandCount for %s\n", def.Name) +} + +type Opcode byte + +const ( + OpConstant Opcode = iota + + OpAdd + + OpPop + + OpSub + OpMul + OpDiv + + OpTrue + OpFalse + + OpEqual + OpNotEqual + OpGreaterThan + + OpMinus + OpBang +) + +type Definition struct { + Name string + OperandWidths []int +} + +var definitions = map[Opcode]*Definition{ + OpConstant: {"OpConstant", []int{2}}, + + OpAdd: {"OpAdd", []int{}}, + + OpPop: {"OpPop", []int{}}, + + OpSub: {"OpSub", []int{}}, + OpMul: {"OpMul", []int{}}, + OpDiv: {"OpDiv", []int{}}, + + OpTrue: {"OpTrue", []int{}}, + OpFalse: {"OpFalse", []int{}}, + + OpEqual: {"OpEqual", []int{}}, + OpNotEqual: {"OpNotEqual", []int{}}, + OpGreaterThan: {"OpGreaterThan", []int{}}, + + OpMinus: {"OpMinus", []int{}}, + OpBang: {"OpBang", []int{}}, +} + +func Lookup(op byte) (*Definition, error) { + def, ok := definitions[Opcode(op)] + if !ok { + return nil, fmt.Errorf("opcode %d undefined", op) + } + + return def, nil +} + +func Make(op Opcode, operands ...int) []byte { + def, ok := definitions[op] + if !ok { + return []byte{} + } + + instructionLen := 1 + for _, w := range def.OperandWidths { + instructionLen += w + } + + instruction := make([]byte, instructionLen) + instruction[0] = byte(op) + + offset := 1 + for i, o := range operands { + width := def.OperandWidths[i] + switch width { + case 2: + binary.BigEndian.PutUint16(instruction[offset:], uint16(o)) + } + offset += width + } + + return instruction +} + +func ReadOperands(def *Definition, ins Instructions) ([]int, int) { + operands := make([]int, len(def.OperandWidths)) + offset := 0 + + for i, width := range def.OperandWidths { + switch width { + case 2: + operands[i] = int(ReadUint16(ins[offset:])) + } + + offset += width + } + + return operands, offset +} + +func ReadUint16(ins Instructions) uint16 { + return binary.BigEndian.Uint16(ins) +} diff --git a/wcig_code_1_2/03/src/monkey/code/code_test.go b/wcig_code_1_2/03/src/monkey/code/code_test.go new file mode 100644 index 0000000..a353025 --- /dev/null +++ b/wcig_code_1_2/03/src/monkey/code/code_test.go @@ -0,0 +1,83 @@ +package code + +import "testing" + +func TestMake(t *testing.T) { + tests := []struct { + op Opcode + operands []int + expected []byte + }{ + {OpConstant, []int{65534}, []byte{byte(OpConstant), 255, 254}}, + {OpAdd, []int{}, []byte{byte(OpAdd)}}, + } + + for _, tt := range tests { + instruction := Make(tt.op, tt.operands...) + + if len(instruction) != len(tt.expected) { + t.Errorf("instruction has wrong length. want=%d, got=%d", + len(tt.expected), len(instruction)) + } + + for i, b := range tt.expected { + if instruction[i] != tt.expected[i] { + t.Errorf("wrong byte at pos %d. want=%d, got=%d", + i, b, instruction[i]) + } + } + } +} + +func TestInstructionsString(t *testing.T) { + instructions := []Instructions{ + Make(OpAdd), + Make(OpConstant, 2), + Make(OpConstant, 65535), + } + + expected := `0000 OpAdd +0001 OpConstant 2 +0004 OpConstant 65535 +` + + concatted := Instructions{} + for _, ins := range instructions { + concatted = append(concatted, ins...) + } + + if concatted.String() != expected { + t.Errorf("instructions wrongly formatted.\nwant=%q\ngot=%q", + expected, concatted.String()) + } +} + +func TestReadOperands(t *testing.T) { + tests := []struct { + op Opcode + operands []int + bytesRead int + }{ + {OpConstant, []int{65535}, 2}, + } + + for _, tt := range tests { + instruction := Make(tt.op, tt.operands...) + + def, err := Lookup(byte(tt.op)) + if err != nil { + t.Fatalf("definition not found: %q\n", err) + } + + operandsRead, n := ReadOperands(def, instruction[1:]) + if n != tt.bytesRead { + t.Fatalf("n wrong. want=%d, got=%d", tt.bytesRead, n) + } + + for i, want := range tt.operands { + if operandsRead[i] != want { + t.Errorf("operand wrong. want=%d, got=%d", want, operandsRead[i]) + } + } + } +} diff --git a/wcig_code_1_2/03/src/monkey/compiler/compiler.go b/wcig_code_1_2/03/src/monkey/compiler/compiler.go new file mode 100644 index 0000000..b78b970 --- /dev/null +++ b/wcig_code_1_2/03/src/monkey/compiler/compiler.go @@ -0,0 +1,140 @@ +package compiler + +import ( + "fmt" + "monkey/ast" + "monkey/code" + "monkey/object" +) + +type Compiler struct { + instructions code.Instructions + constants []object.Object +} + +func New() *Compiler { + return &Compiler{ + instructions: code.Instructions{}, + constants: []object.Object{}, + } +} + +func (c *Compiler) Compile(node ast.Node) error { + switch node := node.(type) { + case *ast.Program: + for _, s := range node.Statements { + err := c.Compile(s) + if err != nil { + return err + } + } + + case *ast.ExpressionStatement: + err := c.Compile(node.Expression) + if err != nil { + return err + } + c.emit(code.OpPop) + + case *ast.InfixExpression: + if node.Operator == "<" { + err := c.Compile(node.Right) + if err != nil { + return err + } + + err = c.Compile(node.Left) + if err != nil { + return err + } + c.emit(code.OpGreaterThan) + return nil + } + + err := c.Compile(node.Left) + if err != nil { + return err + } + + err = c.Compile(node.Right) + if err != nil { + return err + } + + switch node.Operator { + case "+": + c.emit(code.OpAdd) + case "-": + c.emit(code.OpSub) + case "*": + c.emit(code.OpMul) + case "/": + c.emit(code.OpDiv) + case ">": + c.emit(code.OpGreaterThan) + case "==": + c.emit(code.OpEqual) + case "!=": + c.emit(code.OpNotEqual) + default: + return fmt.Errorf("unknown operator %s", node.Operator) + } + + case *ast.IntegerLiteral: + integer := &object.Integer{Value: node.Value} + c.emit(code.OpConstant, c.addConstant(integer)) + + case *ast.Boolean: + if node.Value { + c.emit(code.OpTrue) + } else { + c.emit(code.OpFalse) + } + + case *ast.PrefixExpression: + err := c.Compile(node.Right) + if err != nil { + return err + } + + switch node.Operator { + case "!": + c.emit(code.OpBang) + case "-": + c.emit(code.OpMinus) + default: + return fmt.Errorf("unknown operator %s", node.Operator) + } + } + + return nil +} + +func (c *Compiler) Bytecode() *Bytecode { + return &Bytecode{ + Instructions: c.instructions, + Constants: c.constants, + } +} + +func (c *Compiler) addConstant(obj object.Object) int { + c.constants = append(c.constants, obj) + return len(c.constants) - 1 +} + +func (c *Compiler) emit(op code.Opcode, operands ...int) int { + ins := code.Make(op, operands...) + pos := c.addInstruction(ins) + return pos +} + +func (c *Compiler) addInstruction(ins []byte) int { + posNewInstruction := len(c.instructions) + c.instructions = append(c.instructions, ins...) + return posNewInstruction +} + +type Bytecode struct { + Instructions code.Instructions + Constants []object.Object +} diff --git a/wcig_code_1_2/03/src/monkey/compiler/compiler_test.go b/wcig_code_1_2/03/src/monkey/compiler/compiler_test.go new file mode 100644 index 0000000..7c21807 --- /dev/null +++ b/wcig_code_1_2/03/src/monkey/compiler/compiler_test.go @@ -0,0 +1,277 @@ +package compiler + +import ( + "fmt" + "monkey/ast" + "monkey/code" + "monkey/lexer" + "monkey/object" + "monkey/parser" + "testing" +) + +func TestIntegerArithmetic(t *testing.T) { + tests := []compilerTestCase{ + { + input: "1 + 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpAdd), + code.Make(code.OpPop), + }, + }, + { + input: "1; 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpPop), + code.Make(code.OpConstant, 1), + code.Make(code.OpPop), + }, + }, + { + input: "1 - 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpSub), + code.Make(code.OpPop), + }, + }, + { + input: "1 * 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpMul), + code.Make(code.OpPop), + }, + }, + { + input: "2 / 1", + expectedConstants: []interface{}{2, 1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpDiv), + code.Make(code.OpPop), + }, + }, + { + input: "-1", + expectedConstants: []interface{}{1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpMinus), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestBooleanExpressions(t *testing.T) { + tests := []compilerTestCase{ + { + input: "true", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpTrue), + code.Make(code.OpPop), + }, + }, + { + input: "false", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpFalse), + code.Make(code.OpPop), + }, + }, + { + input: "1 > 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpGreaterThan), + code.Make(code.OpPop), + }, + }, + { + input: "1 < 2", + expectedConstants: []interface{}{2, 1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpGreaterThan), + code.Make(code.OpPop), + }, + }, + { + input: "1 == 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpEqual), + code.Make(code.OpPop), + }, + }, + { + input: "1 != 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpNotEqual), + code.Make(code.OpPop), + }, + }, + { + input: "true == false", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpTrue), + code.Make(code.OpFalse), + code.Make(code.OpEqual), + code.Make(code.OpPop), + }, + }, + { + input: "true != false", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpTrue), + code.Make(code.OpFalse), + code.Make(code.OpNotEqual), + code.Make(code.OpPop), + }, + }, + { + input: "!true", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpTrue), + code.Make(code.OpBang), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +type compilerTestCase struct { + input string + expectedConstants []interface{} + expectedInstructions []code.Instructions +} + +func runCompilerTests(t *testing.T, tests []compilerTestCase) { + t.Helper() + + for _, tt := range tests { + program := parse(tt.input) + + compiler := New() + err := compiler.Compile(program) + if err != nil { + t.Fatalf("compiler error: %s", err) + } + + bytecode := compiler.Bytecode() + + err = testInstructions(tt.expectedInstructions, bytecode.Instructions) + if err != nil { + t.Fatalf("testInstructions failed: %s", err) + } + + err = testConstants(t, tt.expectedConstants, bytecode.Constants) + if err != nil { + t.Fatalf("testConstants failed: %s", err) + } + } +} + +func parse(input string) *ast.Program { + l := lexer.New(input) + p := parser.New(l) + return p.ParseProgram() +} + +func testInstructions( + expected []code.Instructions, + actual code.Instructions, +) error { + concatted := concatInstructions(expected) + + if len(actual) != len(concatted) { + return fmt.Errorf("wrong instructions length.\nwant=%q\ngot =%q", + concatted, actual) + } + + for i, ins := range concatted { + if actual[i] != ins { + return fmt.Errorf("wrong instruction at %d.\nwant=%q\ngot =%q", + i, concatted, actual) + } + } + + return nil +} + +func concatInstructions(s []code.Instructions) code.Instructions { + out := code.Instructions{} + + for _, ins := range s { + out = append(out, ins...) + } + + return out +} + +func testConstants( + t *testing.T, + expected []interface{}, + actual []object.Object, +) error { + if len(expected) != len(actual) { + return fmt.Errorf("wrong number of constants. got=%d, want=%d", + len(actual), len(expected)) + } + + for i, constant := range expected { + switch constant := constant.(type) { + case int: + err := testIntegerObject(int64(constant), actual[i]) + if err != nil { + return fmt.Errorf("constant %d - testIntegerObject failed: %s", + i, err) + } + } + } + + return nil +} + +func testIntegerObject(expected int64, actual object.Object) error { + result, ok := actual.(*object.Integer) + if !ok { + return fmt.Errorf("object is not Integer. got=%T (%+v)", + actual, actual) + } + + if result.Value != expected { + return fmt.Errorf("object has wrong value. got=%d, want=%d", + result.Value, expected) + } + + return nil +} diff --git a/wcig_code_1_2/03/src/monkey/evaluator/builtins.go b/wcig_code_1_2/03/src/monkey/evaluator/builtins.go new file mode 100644 index 0000000..c4fb0f3 --- /dev/null +++ b/wcig_code_1_2/03/src/monkey/evaluator/builtins.go @@ -0,0 +1,117 @@ +package evaluator + +import ( + "fmt" + "monkey/object" +) + +var builtins = map[string]*object.Builtin{ + "len": &object.Builtin{Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", + len(args)) + } + + switch arg := args[0].(type) { + case *object.Array: + return &object.Integer{Value: int64(len(arg.Elements))} + case *object.String: + return &object.Integer{Value: int64(len(arg.Value))} + default: + return newError("argument to `len` not supported, got %s", + args[0].Type()) + } + }, + }, + "puts": &object.Builtin{ + Fn: func(args ...object.Object) object.Object { + for _, arg := range args { + fmt.Println(arg.Inspect()) + } + + return NULL + }, + }, + "first": &object.Builtin{ + Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", + len(args)) + } + if args[0].Type() != object.ARRAY_OBJ { + return newError("argument to `first` must be ARRAY, got %s", + args[0].Type()) + } + + arr := args[0].(*object.Array) + if len(arr.Elements) > 0 { + return arr.Elements[0] + } + + return NULL + }, + }, + "last": &object.Builtin{ + Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", + len(args)) + } + if args[0].Type() != object.ARRAY_OBJ { + return newError("argument to `last` must be ARRAY, got %s", + args[0].Type()) + } + + arr := args[0].(*object.Array) + length := len(arr.Elements) + if length > 0 { + return arr.Elements[length-1] + } + + return NULL + }, + }, + "rest": &object.Builtin{ + Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", + len(args)) + } + if args[0].Type() != object.ARRAY_OBJ { + return newError("argument to `rest` must be ARRAY, got %s", + args[0].Type()) + } + + arr := args[0].(*object.Array) + length := len(arr.Elements) + if length > 0 { + newElements := make([]object.Object, length-1, length-1) + copy(newElements, arr.Elements[1:length]) + return &object.Array{Elements: newElements} + } + + return NULL + }, + }, + "push": &object.Builtin{ + Fn: func(args ...object.Object) object.Object { + if len(args) != 2 { + return newError("wrong number of arguments. got=%d, want=2", + len(args)) + } + if args[0].Type() != object.ARRAY_OBJ { + return newError("argument to `push` must be ARRAY, got %s", + args[0].Type()) + } + + arr := args[0].(*object.Array) + length := len(arr.Elements) + + newElements := make([]object.Object, length+1, length+1) + copy(newElements, arr.Elements) + newElements[length] = args[1] + + return &object.Array{Elements: newElements} + }, + }, +} diff --git a/wcig_code_1_2/03/src/monkey/evaluator/evaluator.go b/wcig_code_1_2/03/src/monkey/evaluator/evaluator.go new file mode 100644 index 0000000..50b2ab5 --- /dev/null +++ b/wcig_code_1_2/03/src/monkey/evaluator/evaluator.go @@ -0,0 +1,442 @@ +package evaluator + +import ( + "fmt" + "monkey/ast" + "monkey/object" +) + +var ( + NULL = &object.Null{} + TRUE = &object.Boolean{Value: true} + FALSE = &object.Boolean{Value: false} +) + +func Eval(node ast.Node, env *object.Environment) object.Object { + switch node := node.(type) { + + // Statements + case *ast.Program: + return evalProgram(node, env) + + case *ast.BlockStatement: + return evalBlockStatement(node, env) + + case *ast.ExpressionStatement: + return Eval(node.Expression, env) + + case *ast.ReturnStatement: + val := Eval(node.ReturnValue, env) + if isError(val) { + return val + } + return &object.ReturnValue{Value: val} + + case *ast.LetStatement: + val := Eval(node.Value, env) + if isError(val) { + return val + } + env.Set(node.Name.Value, val) + + // Expressions + case *ast.IntegerLiteral: + return &object.Integer{Value: node.Value} + + case *ast.StringLiteral: + return &object.String{Value: node.Value} + + case *ast.Boolean: + return nativeBoolToBooleanObject(node.Value) + + case *ast.PrefixExpression: + right := Eval(node.Right, env) + if isError(right) { + return right + } + return evalPrefixExpression(node.Operator, right) + + case *ast.InfixExpression: + left := Eval(node.Left, env) + if isError(left) { + return left + } + + right := Eval(node.Right, env) + if isError(right) { + return right + } + + return evalInfixExpression(node.Operator, left, right) + + case *ast.IfExpression: + return evalIfExpression(node, env) + + case *ast.Identifier: + return evalIdentifier(node, env) + + case *ast.FunctionLiteral: + params := node.Parameters + body := node.Body + return &object.Function{Parameters: params, Env: env, Body: body} + + case *ast.CallExpression: + function := Eval(node.Function, env) + if isError(function) { + return function + } + + args := evalExpressions(node.Arguments, env) + if len(args) == 1 && isError(args[0]) { + return args[0] + } + + return applyFunction(function, args) + + case *ast.ArrayLiteral: + elements := evalExpressions(node.Elements, env) + if len(elements) == 1 && isError(elements[0]) { + return elements[0] + } + return &object.Array{Elements: elements} + + case *ast.IndexExpression: + left := Eval(node.Left, env) + if isError(left) { + return left + } + index := Eval(node.Index, env) + if isError(index) { + return index + } + return evalIndexExpression(left, index) + + case *ast.HashLiteral: + return evalHashLiteral(node, env) + + } + + return nil +} + +func evalProgram(program *ast.Program, env *object.Environment) object.Object { + var result object.Object + + for _, statement := range program.Statements { + result = Eval(statement, env) + + switch result := result.(type) { + case *object.ReturnValue: + return result.Value + case *object.Error: + return result + } + } + + return result +} + +func evalBlockStatement( + block *ast.BlockStatement, + env *object.Environment, +) object.Object { + var result object.Object + + for _, statement := range block.Statements { + result = Eval(statement, env) + + if result != nil { + rt := result.Type() + if rt == object.RETURN_VALUE_OBJ || rt == object.ERROR_OBJ { + return result + } + } + } + + return result +} + +func nativeBoolToBooleanObject(input bool) *object.Boolean { + if input { + return TRUE + } + return FALSE +} + +func evalPrefixExpression(operator string, right object.Object) object.Object { + switch operator { + case "!": + return evalBangOperatorExpression(right) + case "-": + return evalMinusPrefixOperatorExpression(right) + default: + return newError("unknown operator: %s%s", operator, right.Type()) + } +} + +func evalInfixExpression( + operator string, + left, right object.Object, +) object.Object { + switch { + case left.Type() == object.INTEGER_OBJ && right.Type() == object.INTEGER_OBJ: + return evalIntegerInfixExpression(operator, left, right) + case left.Type() == object.STRING_OBJ && right.Type() == object.STRING_OBJ: + return evalStringInfixExpression(operator, left, right) + case operator == "==": + return nativeBoolToBooleanObject(left == right) + case operator == "!=": + return nativeBoolToBooleanObject(left != right) + case left.Type() != right.Type(): + return newError("type mismatch: %s %s %s", + left.Type(), operator, right.Type()) + default: + return newError("unknown operator: %s %s %s", + left.Type(), operator, right.Type()) + } +} + +func evalBangOperatorExpression(right object.Object) object.Object { + switch right { + case TRUE: + return FALSE + case FALSE: + return TRUE + case NULL: + return TRUE + default: + return FALSE + } +} + +func evalMinusPrefixOperatorExpression(right object.Object) object.Object { + if right.Type() != object.INTEGER_OBJ { + return newError("unknown operator: -%s", right.Type()) + } + + value := right.(*object.Integer).Value + return &object.Integer{Value: -value} +} + +func evalIntegerInfixExpression( + operator string, + left, right object.Object, +) object.Object { + leftVal := left.(*object.Integer).Value + rightVal := right.(*object.Integer).Value + + switch operator { + case "+": + return &object.Integer{Value: leftVal + rightVal} + case "-": + return &object.Integer{Value: leftVal - rightVal} + case "*": + return &object.Integer{Value: leftVal * rightVal} + case "/": + return &object.Integer{Value: leftVal / rightVal} + case "<": + return nativeBoolToBooleanObject(leftVal < rightVal) + case ">": + return nativeBoolToBooleanObject(leftVal > rightVal) + case "==": + return nativeBoolToBooleanObject(leftVal == rightVal) + case "!=": + return nativeBoolToBooleanObject(leftVal != rightVal) + default: + return newError("unknown operator: %s %s %s", + left.Type(), operator, right.Type()) + } +} + +func evalStringInfixExpression( + operator string, + left, right object.Object, +) object.Object { + if operator != "+" { + return newError("unknown operator: %s %s %s", + left.Type(), operator, right.Type()) + } + + leftVal := left.(*object.String).Value + rightVal := right.(*object.String).Value + return &object.String{Value: leftVal + rightVal} +} + +func evalIfExpression( + ie *ast.IfExpression, + env *object.Environment, +) object.Object { + condition := Eval(ie.Condition, env) + if isError(condition) { + return condition + } + + if isTruthy(condition) { + return Eval(ie.Consequence, env) + } else if ie.Alternative != nil { + return Eval(ie.Alternative, env) + } else { + return NULL + } +} + +func evalIdentifier( + node *ast.Identifier, + env *object.Environment, +) object.Object { + if val, ok := env.Get(node.Value); ok { + return val + } + + if builtin, ok := builtins[node.Value]; ok { + return builtin + } + + return newError("identifier not found: " + node.Value) +} + +func isTruthy(obj object.Object) bool { + switch obj { + case NULL: + return false + case TRUE: + return true + case FALSE: + return false + default: + return true + } +} + +func newError(format string, a ...interface{}) *object.Error { + return &object.Error{Message: fmt.Sprintf(format, a...)} +} + +func isError(obj object.Object) bool { + if obj != nil { + return obj.Type() == object.ERROR_OBJ + } + return false +} + +func evalExpressions( + exps []ast.Expression, + env *object.Environment, +) []object.Object { + var result []object.Object + + for _, e := range exps { + evaluated := Eval(e, env) + if isError(evaluated) { + return []object.Object{evaluated} + } + result = append(result, evaluated) + } + + return result +} + +func applyFunction(fn object.Object, args []object.Object) object.Object { + switch fn := fn.(type) { + + case *object.Function: + extendedEnv := extendFunctionEnv(fn, args) + evaluated := Eval(fn.Body, extendedEnv) + return unwrapReturnValue(evaluated) + + case *object.Builtin: + return fn.Fn(args...) + + default: + return newError("not a function: %s", fn.Type()) + } +} + +func extendFunctionEnv( + fn *object.Function, + args []object.Object, +) *object.Environment { + env := object.NewEnclosedEnvironment(fn.Env) + + for paramIdx, param := range fn.Parameters { + env.Set(param.Value, args[paramIdx]) + } + + return env +} + +func unwrapReturnValue(obj object.Object) object.Object { + if returnValue, ok := obj.(*object.ReturnValue); ok { + return returnValue.Value + } + + return obj +} + +func evalIndexExpression(left, index object.Object) object.Object { + switch { + case left.Type() == object.ARRAY_OBJ && index.Type() == object.INTEGER_OBJ: + return evalArrayIndexExpression(left, index) + case left.Type() == object.HASH_OBJ: + return evalHashIndexExpression(left, index) + default: + return newError("index operator not supported: %s", left.Type()) + } +} + +func evalArrayIndexExpression(array, index object.Object) object.Object { + arrayObject := array.(*object.Array) + idx := index.(*object.Integer).Value + max := int64(len(arrayObject.Elements) - 1) + + if idx < 0 || idx > max { + return NULL + } + + return arrayObject.Elements[idx] +} + +func evalHashLiteral( + node *ast.HashLiteral, + env *object.Environment, +) object.Object { + pairs := make(map[object.HashKey]object.HashPair) + + for keyNode, valueNode := range node.Pairs { + key := Eval(keyNode, env) + if isError(key) { + return key + } + + hashKey, ok := key.(object.Hashable) + if !ok { + return newError("unusable as hash key: %s", key.Type()) + } + + value := Eval(valueNode, env) + if isError(value) { + return value + } + + hashed := hashKey.HashKey() + pairs[hashed] = object.HashPair{Key: key, Value: value} + } + + return &object.Hash{Pairs: pairs} +} + +func evalHashIndexExpression(hash, index object.Object) object.Object { + hashObject := hash.(*object.Hash) + + key, ok := index.(object.Hashable) + if !ok { + return newError("unusable as hash key: %s", index.Type()) + } + + pair, ok := hashObject.Pairs[key.HashKey()] + if !ok { + return NULL + } + + return pair.Value +} diff --git a/wcig_code_1_2/03/src/monkey/evaluator/evaluator_test.go b/wcig_code_1_2/03/src/monkey/evaluator/evaluator_test.go new file mode 100644 index 0000000..2304e6f --- /dev/null +++ b/wcig_code_1_2/03/src/monkey/evaluator/evaluator_test.go @@ -0,0 +1,629 @@ +package evaluator + +import ( + "monkey/lexer" + "monkey/object" + "monkey/parser" + "testing" +) + +func TestEvalIntegerExpression(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"5", 5}, + {"10", 10}, + {"-5", -5}, + {"-10", -10}, + {"5 + 5 + 5 + 5 - 10", 10}, + {"2 * 2 * 2 * 2 * 2", 32}, + {"-50 + 100 + -50", 0}, + {"5 * 2 + 10", 20}, + {"5 + 2 * 10", 25}, + {"20 + 2 * -10", 0}, + {"50 / 2 * 2 + 10", 60}, + {"2 * (5 + 10)", 30}, + {"3 * 3 * 3 + 10", 37}, + {"3 * (3 * 3) + 10", 37}, + {"(5 + 10 * 2 + 15 / 3) * 2 + -10", 50}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testIntegerObject(t, evaluated, tt.expected) + } +} + +func TestEvalBooleanExpression(t *testing.T) { + tests := []struct { + input string + expected bool + }{ + {"true", true}, + {"false", false}, + {"1 < 2", true}, + {"1 > 2", false}, + {"1 < 1", false}, + {"1 > 1", false}, + {"1 == 1", true}, + {"1 != 1", false}, + {"1 == 2", false}, + {"1 != 2", true}, + {"true == true", true}, + {"false == false", true}, + {"true == false", false}, + {"true != false", true}, + {"false != true", true}, + {"(1 < 2) == true", true}, + {"(1 < 2) == false", false}, + {"(1 > 2) == true", false}, + {"(1 > 2) == false", true}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testBooleanObject(t, evaluated, tt.expected) + } +} + +func TestBangOperator(t *testing.T) { + tests := []struct { + input string + expected bool + }{ + {"!true", false}, + {"!false", true}, + {"!5", false}, + {"!!true", true}, + {"!!false", false}, + {"!!5", true}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testBooleanObject(t, evaluated, tt.expected) + } +} + +func TestIfElseExpressions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + {"if (true) { 10 }", 10}, + {"if (false) { 10 }", nil}, + {"if (1) { 10 }", 10}, + {"if (1 < 2) { 10 }", 10}, + {"if (1 > 2) { 10 }", nil}, + {"if (1 > 2) { 10 } else { 20 }", 20}, + {"if (1 < 2) { 10 } else { 20 }", 10}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + integer, ok := tt.expected.(int) + if ok { + testIntegerObject(t, evaluated, int64(integer)) + } else { + testNullObject(t, evaluated) + } + } +} + +func TestReturnStatements(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"return 10;", 10}, + {"return 10; 9;", 10}, + {"return 2 * 5; 9;", 10}, + {"9; return 2 * 5; 9;", 10}, + {"if (10 > 1) { return 10; }", 10}, + { + ` +if (10 > 1) { + if (10 > 1) { + return 10; + } + + return 1; +} +`, + 10, + }, + { + ` +let f = fn(x) { + return x; + x + 10; +}; +f(10);`, + 10, + }, + { + ` +let f = fn(x) { + let result = x + 10; + return result; + return 10; +}; +f(10);`, + 20, + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testIntegerObject(t, evaluated, tt.expected) + } +} + +func TestErrorHandling(t *testing.T) { + tests := []struct { + input string + expectedMessage string + }{ + { + "5 + true;", + "type mismatch: INTEGER + BOOLEAN", + }, + { + "5 + true; 5;", + "type mismatch: INTEGER + BOOLEAN", + }, + { + "-true", + "unknown operator: -BOOLEAN", + }, + { + "true + false;", + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + "true + false + true + false;", + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + "5; true + false; 5", + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + `"Hello" - "World"`, + "unknown operator: STRING - STRING", + }, + { + "if (10 > 1) { true + false; }", + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + ` +if (10 > 1) { + if (10 > 1) { + return true + false; + } + + return 1; +} +`, + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + "foobar", + "identifier not found: foobar", + }, + { + `{"name": "Monkey"}[fn(x) { x }];`, + "unusable as hash key: FUNCTION", + }, + { + `999[1]`, + "index operator not supported: INTEGER", + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + + errObj, ok := evaluated.(*object.Error) + if !ok { + t.Errorf("no error object returned. got=%T(%+v)", + evaluated, evaluated) + continue + } + + if errObj.Message != tt.expectedMessage { + t.Errorf("wrong error message. expected=%q, got=%q", + tt.expectedMessage, errObj.Message) + } + } +} + +func TestLetStatements(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"let a = 5; a;", 5}, + {"let a = 5 * 5; a;", 25}, + {"let a = 5; let b = a; b;", 5}, + {"let a = 5; let b = a; let c = a + b + 5; c;", 15}, + } + + for _, tt := range tests { + testIntegerObject(t, testEval(tt.input), tt.expected) + } +} + +func TestFunctionObject(t *testing.T) { + input := "fn(x) { x + 2; };" + + evaluated := testEval(input) + fn, ok := evaluated.(*object.Function) + if !ok { + t.Fatalf("object is not Function. got=%T (%+v)", evaluated, evaluated) + } + + if len(fn.Parameters) != 1 { + t.Fatalf("function has wrong parameters. Parameters=%+v", + fn.Parameters) + } + + if fn.Parameters[0].String() != "x" { + t.Fatalf("parameter is not 'x'. got=%q", fn.Parameters[0]) + } + + expectedBody := "(x + 2)" + + if fn.Body.String() != expectedBody { + t.Fatalf("body is not %q. got=%q", expectedBody, fn.Body.String()) + } +} + +func TestFunctionApplication(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"let identity = fn(x) { x; }; identity(5);", 5}, + {"let identity = fn(x) { return x; }; identity(5);", 5}, + {"let double = fn(x) { x * 2; }; double(5);", 10}, + {"let add = fn(x, y) { x + y; }; add(5, 5);", 10}, + {"let add = fn(x, y) { x + y; }; add(5 + 5, add(5, 5));", 20}, + {"fn(x) { x; }(5)", 5}, + } + + for _, tt := range tests { + testIntegerObject(t, testEval(tt.input), tt.expected) + } +} + +func TestEnclosingEnvironments(t *testing.T) { + input := ` +let first = 10; +let second = 10; +let third = 10; + +let ourFunction = fn(first) { + let second = 20; + + first + second + third; +}; + +ourFunction(20) + first + second;` + + testIntegerObject(t, testEval(input), 70) +} + +func TestClosures(t *testing.T) { + input := ` +let newAdder = fn(x) { + fn(y) { x + y }; +}; + +let addTwo = newAdder(2); +addTwo(2);` + + testIntegerObject(t, testEval(input), 4) +} + +func TestStringLiteral(t *testing.T) { + input := `"Hello World!"` + + evaluated := testEval(input) + str, ok := evaluated.(*object.String) + if !ok { + t.Fatalf("object is not String. got=%T (%+v)", evaluated, evaluated) + } + + if str.Value != "Hello World!" { + t.Errorf("String has wrong value. got=%q", str.Value) + } +} + +func TestStringConcatenation(t *testing.T) { + input := `"Hello" + " " + "World!"` + + evaluated := testEval(input) + str, ok := evaluated.(*object.String) + if !ok { + t.Fatalf("object is not String. got=%T (%+v)", evaluated, evaluated) + } + + if str.Value != "Hello World!" { + t.Errorf("String has wrong value. got=%q", str.Value) + } +} + +func TestBuiltinFunctions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + {`len("")`, 0}, + {`len("four")`, 4}, + {`len("hello world")`, 11}, + {`len(1)`, "argument to `len` not supported, got INTEGER"}, + {`len("one", "two")`, "wrong number of arguments. got=2, want=1"}, + {`len([1, 2, 3])`, 3}, + {`len([])`, 0}, + {`puts("hello", "world!")`, nil}, + {`first([1, 2, 3])`, 1}, + {`first([])`, nil}, + {`first(1)`, "argument to `first` must be ARRAY, got INTEGER"}, + {`last([1, 2, 3])`, 3}, + {`last([])`, nil}, + {`last(1)`, "argument to `last` must be ARRAY, got INTEGER"}, + {`rest([1, 2, 3])`, []int{2, 3}}, + {`rest([])`, nil}, + {`push([], 1)`, []int{1}}, + {`push(1, 1)`, "argument to `push` must be ARRAY, got INTEGER"}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + + switch expected := tt.expected.(type) { + case int: + testIntegerObject(t, evaluated, int64(expected)) + case nil: + testNullObject(t, evaluated) + case string: + errObj, ok := evaluated.(*object.Error) + if !ok { + t.Errorf("object is not Error. got=%T (%+v)", + evaluated, evaluated) + continue + } + if errObj.Message != expected { + t.Errorf("wrong error message. expected=%q, got=%q", + expected, errObj.Message) + } + case []int: + array, ok := evaluated.(*object.Array) + if !ok { + t.Errorf("obj not Array. got=%T (%+v)", evaluated, evaluated) + continue + } + + if len(array.Elements) != len(expected) { + t.Errorf("wrong num of elements. want=%d, got=%d", + len(expected), len(array.Elements)) + continue + } + + for i, expectedElem := range expected { + testIntegerObject(t, array.Elements[i], int64(expectedElem)) + } + } + } +} + +func TestArrayLiterals(t *testing.T) { + input := "[1, 2 * 2, 3 + 3]" + + evaluated := testEval(input) + result, ok := evaluated.(*object.Array) + if !ok { + t.Fatalf("object is not Array. got=%T (%+v)", evaluated, evaluated) + } + + if len(result.Elements) != 3 { + t.Fatalf("array has wrong num of elements. got=%d", + len(result.Elements)) + } + + testIntegerObject(t, result.Elements[0], 1) + testIntegerObject(t, result.Elements[1], 4) + testIntegerObject(t, result.Elements[2], 6) +} + +func TestArrayIndexExpressions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + { + "[1, 2, 3][0]", + 1, + }, + { + "[1, 2, 3][1]", + 2, + }, + { + "[1, 2, 3][2]", + 3, + }, + { + "let i = 0; [1][i];", + 1, + }, + { + "[1, 2, 3][1 + 1];", + 3, + }, + { + "let myArray = [1, 2, 3]; myArray[2];", + 3, + }, + { + "let myArray = [1, 2, 3]; myArray[0] + myArray[1] + myArray[2];", + 6, + }, + { + "let myArray = [1, 2, 3]; let i = myArray[0]; myArray[i]", + 2, + }, + { + "[1, 2, 3][3]", + nil, + }, + { + "[1, 2, 3][-1]", + nil, + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + integer, ok := tt.expected.(int) + if ok { + testIntegerObject(t, evaluated, int64(integer)) + } else { + testNullObject(t, evaluated) + } + } +} + +func TestHashLiterals(t *testing.T) { + input := `let two = "two"; + { + "one": 10 - 9, + two: 1 + 1, + "thr" + "ee": 6 / 2, + 4: 4, + true: 5, + false: 6 + }` + + evaluated := testEval(input) + result, ok := evaluated.(*object.Hash) + if !ok { + t.Fatalf("Eval didn't return Hash. got=%T (%+v)", evaluated, evaluated) + } + + expected := map[object.HashKey]int64{ + (&object.String{Value: "one"}).HashKey(): 1, + (&object.String{Value: "two"}).HashKey(): 2, + (&object.String{Value: "three"}).HashKey(): 3, + (&object.Integer{Value: 4}).HashKey(): 4, + TRUE.HashKey(): 5, + FALSE.HashKey(): 6, + } + + if len(result.Pairs) != len(expected) { + t.Fatalf("Hash has wrong num of pairs. got=%d", len(result.Pairs)) + } + + for expectedKey, expectedValue := range expected { + pair, ok := result.Pairs[expectedKey] + if !ok { + t.Errorf("no pair for given key in Pairs") + } + + testIntegerObject(t, pair.Value, expectedValue) + } +} + +func TestHashIndexExpressions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + { + `{"foo": 5}["foo"]`, + 5, + }, + { + `{"foo": 5}["bar"]`, + nil, + }, + { + `let key = "foo"; {"foo": 5}[key]`, + 5, + }, + { + `{}["foo"]`, + nil, + }, + { + `{5: 5}[5]`, + 5, + }, + { + `{true: 5}[true]`, + 5, + }, + { + `{false: 5}[false]`, + 5, + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + integer, ok := tt.expected.(int) + if ok { + testIntegerObject(t, evaluated, int64(integer)) + } else { + testNullObject(t, evaluated) + } + } +} +func testEval(input string) object.Object { + l := lexer.New(input) + p := parser.New(l) + program := p.ParseProgram() + env := object.NewEnvironment() + + return Eval(program, env) +} + +func testIntegerObject(t *testing.T, obj object.Object, expected int64) bool { + result, ok := obj.(*object.Integer) + if !ok { + t.Errorf("object is not Integer. got=%T (%+v)", obj, obj) + return false + } + if result.Value != expected { + t.Errorf("object has wrong value. got=%d, want=%d", + result.Value, expected) + return false + } + + return true +} + +func testBooleanObject(t *testing.T, obj object.Object, expected bool) bool { + result, ok := obj.(*object.Boolean) + if !ok { + t.Errorf("object is not Boolean. got=%T (%+v)", obj, obj) + return false + } + if result.Value != expected { + t.Errorf("object has wrong value. got=%t, want=%t", + result.Value, expected) + return false + } + return true +} + +func testNullObject(t *testing.T, obj object.Object) bool { + if obj != NULL { + t.Errorf("object is not NULL. got=%T (%+v)", obj, obj) + return false + } + return true +} diff --git a/wcig_code_1_2/03/src/monkey/go.mod b/wcig_code_1_2/03/src/monkey/go.mod new file mode 100644 index 0000000..3064854 --- /dev/null +++ b/wcig_code_1_2/03/src/monkey/go.mod @@ -0,0 +1,3 @@ +module monkey + +go 1.14 diff --git a/wcig_code_1_2/03/src/monkey/lexer/lexer.go b/wcig_code_1_2/03/src/monkey/lexer/lexer.go new file mode 100644 index 0000000..db4f5f7 --- /dev/null +++ b/wcig_code_1_2/03/src/monkey/lexer/lexer.go @@ -0,0 +1,157 @@ +package lexer + +import "monkey/token" + +type Lexer struct { + input string + position int // current position in input (points to current char) + readPosition int // current reading position in input (after current char) + ch byte // current char under examination +} + +func New(input string) *Lexer { + l := &Lexer{input: input} + l.readChar() + return l +} + +func (l *Lexer) NextToken() token.Token { + var tok token.Token + + l.skipWhitespace() + + switch l.ch { + case '=': + if l.peekChar() == '=' { + ch := l.ch + l.readChar() + literal := string(ch) + string(l.ch) + tok = token.Token{Type: token.EQ, Literal: literal} + } else { + tok = newToken(token.ASSIGN, l.ch) + } + case '+': + tok = newToken(token.PLUS, l.ch) + case '-': + tok = newToken(token.MINUS, l.ch) + case '!': + if l.peekChar() == '=' { + ch := l.ch + l.readChar() + literal := string(ch) + string(l.ch) + tok = token.Token{Type: token.NOT_EQ, Literal: literal} + } else { + tok = newToken(token.BANG, l.ch) + } + case '/': + tok = newToken(token.SLASH, l.ch) + case '*': + tok = newToken(token.ASTERISK, l.ch) + case '<': + tok = newToken(token.LT, l.ch) + case '>': + tok = newToken(token.GT, l.ch) + case ';': + tok = newToken(token.SEMICOLON, l.ch) + case ':': + tok = newToken(token.COLON, l.ch) + case ',': + tok = newToken(token.COMMA, l.ch) + case '{': + tok = newToken(token.LBRACE, l.ch) + case '}': + tok = newToken(token.RBRACE, l.ch) + case '(': + tok = newToken(token.LPAREN, l.ch) + case ')': + tok = newToken(token.RPAREN, l.ch) + case '"': + tok.Type = token.STRING + tok.Literal = l.readString() + case '[': + tok = newToken(token.LBRACKET, l.ch) + case ']': + tok = newToken(token.RBRACKET, l.ch) + case 0: + tok.Literal = "" + tok.Type = token.EOF + default: + if isLetter(l.ch) { + tok.Literal = l.readIdentifier() + tok.Type = token.LookupIdent(tok.Literal) + return tok + } else if isDigit(l.ch) { + tok.Type = token.INT + tok.Literal = l.readNumber() + return tok + } else { + tok = newToken(token.ILLEGAL, l.ch) + } + } + + l.readChar() + return tok +} + +func (l *Lexer) skipWhitespace() { + for l.ch == ' ' || l.ch == '\t' || l.ch == '\n' || l.ch == '\r' { + l.readChar() + } +} + +func (l *Lexer) readChar() { + if l.readPosition >= len(l.input) { + l.ch = 0 + } else { + l.ch = l.input[l.readPosition] + } + l.position = l.readPosition + l.readPosition += 1 +} + +func (l *Lexer) peekChar() byte { + if l.readPosition >= len(l.input) { + return 0 + } else { + return l.input[l.readPosition] + } +} + +func (l *Lexer) readIdentifier() string { + position := l.position + for isLetter(l.ch) { + l.readChar() + } + return l.input[position:l.position] +} + +func (l *Lexer) readNumber() string { + position := l.position + for isDigit(l.ch) { + l.readChar() + } + return l.input[position:l.position] +} + +func (l *Lexer) readString() string { + position := l.position + 1 + for { + l.readChar() + if l.ch == '"' || l.ch == 0 { + break + } + } + return l.input[position:l.position] +} + +func isLetter(ch byte) bool { + return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' +} + +func isDigit(ch byte) bool { + return '0' <= ch && ch <= '9' +} + +func newToken(tokenType token.TokenType, ch byte) token.Token { + return token.Token{Type: tokenType, Literal: string(ch)} +} diff --git a/wcig_code_1_2/03/src/monkey/lexer/lexer_test.go b/wcig_code_1_2/03/src/monkey/lexer/lexer_test.go new file mode 100644 index 0000000..e4e64a4 --- /dev/null +++ b/wcig_code_1_2/03/src/monkey/lexer/lexer_test.go @@ -0,0 +1,143 @@ +package lexer + +import ( + "testing" + + "monkey/token" +) + +func TestNextToken(t *testing.T) { + input := `let five = 5; +let ten = 10; + +let add = fn(x, y) { + x + y; +}; + +let result = add(five, ten); +!-/*5; +5 < 10 > 5; + +if (5 < 10) { + return true; +} else { + return false; +} + +10 == 10; +10 != 9; +"foobar" +"foo bar" +[1, 2]; +{"foo": "bar"} +` + + tests := []struct { + expectedType token.TokenType + expectedLiteral string + }{ + {token.LET, "let"}, + {token.IDENT, "five"}, + {token.ASSIGN, "="}, + {token.INT, "5"}, + {token.SEMICOLON, ";"}, + {token.LET, "let"}, + {token.IDENT, "ten"}, + {token.ASSIGN, "="}, + {token.INT, "10"}, + {token.SEMICOLON, ";"}, + {token.LET, "let"}, + {token.IDENT, "add"}, + {token.ASSIGN, "="}, + {token.FUNCTION, "fn"}, + {token.LPAREN, "("}, + {token.IDENT, "x"}, + {token.COMMA, ","}, + {token.IDENT, "y"}, + {token.RPAREN, ")"}, + {token.LBRACE, "{"}, + {token.IDENT, "x"}, + {token.PLUS, "+"}, + {token.IDENT, "y"}, + {token.SEMICOLON, ";"}, + {token.RBRACE, "}"}, + {token.SEMICOLON, ";"}, + {token.LET, "let"}, + {token.IDENT, "result"}, + {token.ASSIGN, "="}, + {token.IDENT, "add"}, + {token.LPAREN, "("}, + {token.IDENT, "five"}, + {token.COMMA, ","}, + {token.IDENT, "ten"}, + {token.RPAREN, ")"}, + {token.SEMICOLON, ";"}, + {token.BANG, "!"}, + {token.MINUS, "-"}, + {token.SLASH, "/"}, + {token.ASTERISK, "*"}, + {token.INT, "5"}, + {token.SEMICOLON, ";"}, + {token.INT, "5"}, + {token.LT, "<"}, + {token.INT, "10"}, + {token.GT, ">"}, + {token.INT, "5"}, + {token.SEMICOLON, ";"}, + {token.IF, "if"}, + {token.LPAREN, "("}, + {token.INT, "5"}, + {token.LT, "<"}, + {token.INT, "10"}, + {token.RPAREN, ")"}, + {token.LBRACE, "{"}, + {token.RETURN, "return"}, + {token.TRUE, "true"}, + {token.SEMICOLON, ";"}, + {token.RBRACE, "}"}, + {token.ELSE, "else"}, + {token.LBRACE, "{"}, + {token.RETURN, "return"}, + {token.FALSE, "false"}, + {token.SEMICOLON, ";"}, + {token.RBRACE, "}"}, + {token.INT, "10"}, + {token.EQ, "=="}, + {token.INT, "10"}, + {token.SEMICOLON, ";"}, + {token.INT, "10"}, + {token.NOT_EQ, "!="}, + {token.INT, "9"}, + {token.SEMICOLON, ";"}, + {token.STRING, "foobar"}, + {token.STRING, "foo bar"}, + {token.LBRACKET, "["}, + {token.INT, "1"}, + {token.COMMA, ","}, + {token.INT, "2"}, + {token.RBRACKET, "]"}, + {token.SEMICOLON, ";"}, + {token.LBRACE, "{"}, + {token.STRING, "foo"}, + {token.COLON, ":"}, + {token.STRING, "bar"}, + {token.RBRACE, "}"}, + {token.EOF, ""}, + } + + l := New(input) + + for i, tt := range tests { + tok := l.NextToken() + + if tok.Type != tt.expectedType { + t.Fatalf("tests[%d] - tokentype wrong. expected=%q, got=%q", + i, tt.expectedType, tok.Type) + } + + if tok.Literal != tt.expectedLiteral { + t.Fatalf("tests[%d] - literal wrong. expected=%q, got=%q", + i, tt.expectedLiteral, tok.Literal) + } + } +} diff --git a/wcig_code_1_2/03/src/monkey/main.go b/wcig_code_1_2/03/src/monkey/main.go new file mode 100644 index 0000000..1d27138 --- /dev/null +++ b/wcig_code_1_2/03/src/monkey/main.go @@ -0,0 +1,19 @@ +package main + +import ( + "fmt" + "monkey/repl" + "os" + "os/user" +) + +func main() { + user, err := user.Current() + if err != nil { + panic(err) + } + fmt.Printf("Hello %s! This is the Monkey programming language!\n", + user.Username) + fmt.Printf("Feel free to type in commands\n") + repl.Start(os.Stdin, os.Stdout) +} diff --git a/wcig_code_1_2/03/src/monkey/object/environment.go b/wcig_code_1_2/03/src/monkey/object/environment.go new file mode 100644 index 0000000..6f31070 --- /dev/null +++ b/wcig_code_1_2/03/src/monkey/object/environment.go @@ -0,0 +1,30 @@ +package object + +func NewEnclosedEnvironment(outer *Environment) *Environment { + env := NewEnvironment() + env.outer = outer + return env +} + +func NewEnvironment() *Environment { + s := make(map[string]Object) + return &Environment{store: s, outer: nil} +} + +type Environment struct { + store map[string]Object + outer *Environment +} + +func (e *Environment) Get(name string) (Object, bool) { + obj, ok := e.store[name] + if !ok && e.outer != nil { + obj, ok = e.outer.Get(name) + } + return obj, ok +} + +func (e *Environment) Set(name string, val Object) Object { + e.store[name] = val + return val +} diff --git a/wcig_code_1_2/03/src/monkey/object/object.go b/wcig_code_1_2/03/src/monkey/object/object.go new file mode 100644 index 0000000..2c2a1b0 --- /dev/null +++ b/wcig_code_1_2/03/src/monkey/object/object.go @@ -0,0 +1,182 @@ +package object + +import ( + "bytes" + "fmt" + "hash/fnv" + "monkey/ast" + "strings" +) + +type BuiltinFunction func(args ...Object) Object + +type ObjectType string + +const ( + NULL_OBJ = "NULL" + ERROR_OBJ = "ERROR" + + INTEGER_OBJ = "INTEGER" + BOOLEAN_OBJ = "BOOLEAN" + STRING_OBJ = "STRING" + + RETURN_VALUE_OBJ = "RETURN_VALUE" + + FUNCTION_OBJ = "FUNCTION" + BUILTIN_OBJ = "BUILTIN" + + ARRAY_OBJ = "ARRAY" + HASH_OBJ = "HASH" +) + +type HashKey struct { + Type ObjectType + Value uint64 +} + +type Hashable interface { + HashKey() HashKey +} + +type Object interface { + Type() ObjectType + Inspect() string +} + +type Integer struct { + Value int64 +} + +func (i *Integer) Type() ObjectType { return INTEGER_OBJ } +func (i *Integer) Inspect() string { return fmt.Sprintf("%d", i.Value) } +func (i *Integer) HashKey() HashKey { + return HashKey{Type: i.Type(), Value: uint64(i.Value)} +} + +type Boolean struct { + Value bool +} + +func (b *Boolean) Type() ObjectType { return BOOLEAN_OBJ } +func (b *Boolean) Inspect() string { return fmt.Sprintf("%t", b.Value) } +func (b *Boolean) HashKey() HashKey { + var value uint64 + + if b.Value { + value = 1 + } else { + value = 0 + } + + return HashKey{Type: b.Type(), Value: value} +} + +type Null struct{} + +func (n *Null) Type() ObjectType { return NULL_OBJ } +func (n *Null) Inspect() string { return "null" } + +type ReturnValue struct { + Value Object +} + +func (rv *ReturnValue) Type() ObjectType { return RETURN_VALUE_OBJ } +func (rv *ReturnValue) Inspect() string { return rv.Value.Inspect() } + +type Error struct { + Message string +} + +func (e *Error) Type() ObjectType { return ERROR_OBJ } +func (e *Error) Inspect() string { return "ERROR: " + e.Message } + +type Function struct { + Parameters []*ast.Identifier + Body *ast.BlockStatement + Env *Environment +} + +func (f *Function) Type() ObjectType { return FUNCTION_OBJ } +func (f *Function) Inspect() string { + var out bytes.Buffer + + params := []string{} + for _, p := range f.Parameters { + params = append(params, p.String()) + } + + out.WriteString("fn") + out.WriteString("(") + out.WriteString(strings.Join(params, ", ")) + out.WriteString(") {\n") + out.WriteString(f.Body.String()) + out.WriteString("\n}") + + return out.String() +} + +type String struct { + Value string +} + +func (s *String) Type() ObjectType { return STRING_OBJ } +func (s *String) Inspect() string { return s.Value } +func (s *String) HashKey() HashKey { + h := fnv.New64a() + h.Write([]byte(s.Value)) + + return HashKey{Type: s.Type(), Value: h.Sum64()} +} + +type Builtin struct { + Fn BuiltinFunction +} + +func (b *Builtin) Type() ObjectType { return BUILTIN_OBJ } +func (b *Builtin) Inspect() string { return "builtin function" } + +type Array struct { + Elements []Object +} + +func (ao *Array) Type() ObjectType { return ARRAY_OBJ } +func (ao *Array) Inspect() string { + var out bytes.Buffer + + elements := []string{} + for _, e := range ao.Elements { + elements = append(elements, e.Inspect()) + } + + out.WriteString("[") + out.WriteString(strings.Join(elements, ", ")) + out.WriteString("]") + + return out.String() +} + +type HashPair struct { + Key Object + Value Object +} + +type Hash struct { + Pairs map[HashKey]HashPair +} + +func (h *Hash) Type() ObjectType { return HASH_OBJ } +func (h *Hash) Inspect() string { + var out bytes.Buffer + + pairs := []string{} + for _, pair := range h.Pairs { + pairs = append(pairs, fmt.Sprintf("%s: %s", + pair.Key.Inspect(), pair.Value.Inspect())) + } + + out.WriteString("{") + out.WriteString(strings.Join(pairs, ", ")) + out.WriteString("}") + + return out.String() +} diff --git a/wcig_code_1_2/03/src/monkey/object/object_test.go b/wcig_code_1_2/03/src/monkey/object/object_test.go new file mode 100644 index 0000000..63228a2 --- /dev/null +++ b/wcig_code_1_2/03/src/monkey/object/object_test.go @@ -0,0 +1,60 @@ +package object + +import "testing" + +func TestStringHashKey(t *testing.T) { + hello1 := &String{Value: "Hello World"} + hello2 := &String{Value: "Hello World"} + diff1 := &String{Value: "My name is johnny"} + diff2 := &String{Value: "My name is johnny"} + + if hello1.HashKey() != hello2.HashKey() { + t.Errorf("strings with same content have different hash keys") + } + + if diff1.HashKey() != diff2.HashKey() { + t.Errorf("strings with same content have different hash keys") + } + + if hello1.HashKey() == diff1.HashKey() { + t.Errorf("strings with different content have same hash keys") + } +} + +func TestBooleanHashKey(t *testing.T) { + true1 := &Boolean{Value: true} + true2 := &Boolean{Value: true} + false1 := &Boolean{Value: false} + false2 := &Boolean{Value: false} + + if true1.HashKey() != true2.HashKey() { + t.Errorf("trues do not have same hash key") + } + + if false1.HashKey() != false2.HashKey() { + t.Errorf("falses do not have same hash key") + } + + if true1.HashKey() == false1.HashKey() { + t.Errorf("true has same hash key as false") + } +} + +func TestIntegerHashKey(t *testing.T) { + one1 := &Integer{Value: 1} + one2 := &Integer{Value: 1} + two1 := &Integer{Value: 2} + two2 := &Integer{Value: 2} + + if one1.HashKey() != one2.HashKey() { + t.Errorf("integers with same content have twoerent hash keys") + } + + if two1.HashKey() != two2.HashKey() { + t.Errorf("integers with same content have twoerent hash keys") + } + + if one1.HashKey() == two1.HashKey() { + t.Errorf("integers with twoerent content have same hash keys") + } +} diff --git a/wcig_code_1_2/03/src/monkey/parser/parser.go b/wcig_code_1_2/03/src/monkey/parser/parser.go new file mode 100644 index 0000000..0a1f19c --- /dev/null +++ b/wcig_code_1_2/03/src/monkey/parser/parser.go @@ -0,0 +1,491 @@ +package parser + +import ( + "fmt" + "monkey/ast" + "monkey/lexer" + "monkey/token" + "strconv" +) + +const ( + _ int = iota + LOWEST + EQUALS // == + LESSGREATER // > or < + SUM // + + PRODUCT // * + PREFIX // -X or !X + CALL // myFunction(X) + INDEX // array[index] +) + +var precedences = map[token.TokenType]int{ + token.EQ: EQUALS, + token.NOT_EQ: EQUALS, + token.LT: LESSGREATER, + token.GT: LESSGREATER, + token.PLUS: SUM, + token.MINUS: SUM, + token.SLASH: PRODUCT, + token.ASTERISK: PRODUCT, + token.LPAREN: CALL, + token.LBRACKET: INDEX, +} + +type ( + prefixParseFn func() ast.Expression + infixParseFn func(ast.Expression) ast.Expression +) + +type Parser struct { + l *lexer.Lexer + errors []string + + curToken token.Token + peekToken token.Token + + prefixParseFns map[token.TokenType]prefixParseFn + infixParseFns map[token.TokenType]infixParseFn +} + +func New(l *lexer.Lexer) *Parser { + p := &Parser{ + l: l, + errors: []string{}, + } + + p.prefixParseFns = make(map[token.TokenType]prefixParseFn) + p.registerPrefix(token.IDENT, p.parseIdentifier) + p.registerPrefix(token.INT, p.parseIntegerLiteral) + p.registerPrefix(token.STRING, p.parseStringLiteral) + p.registerPrefix(token.BANG, p.parsePrefixExpression) + p.registerPrefix(token.MINUS, p.parsePrefixExpression) + p.registerPrefix(token.TRUE, p.parseBoolean) + p.registerPrefix(token.FALSE, p.parseBoolean) + p.registerPrefix(token.LPAREN, p.parseGroupedExpression) + p.registerPrefix(token.IF, p.parseIfExpression) + p.registerPrefix(token.FUNCTION, p.parseFunctionLiteral) + p.registerPrefix(token.LBRACKET, p.parseArrayLiteral) + p.registerPrefix(token.LBRACE, p.parseHashLiteral) + + p.infixParseFns = make(map[token.TokenType]infixParseFn) + p.registerInfix(token.PLUS, p.parseInfixExpression) + p.registerInfix(token.MINUS, p.parseInfixExpression) + p.registerInfix(token.SLASH, p.parseInfixExpression) + p.registerInfix(token.ASTERISK, p.parseInfixExpression) + p.registerInfix(token.EQ, p.parseInfixExpression) + p.registerInfix(token.NOT_EQ, p.parseInfixExpression) + p.registerInfix(token.LT, p.parseInfixExpression) + p.registerInfix(token.GT, p.parseInfixExpression) + + p.registerInfix(token.LPAREN, p.parseCallExpression) + p.registerInfix(token.LBRACKET, p.parseIndexExpression) + + // Read two tokens, so curToken and peekToken are both set + p.nextToken() + p.nextToken() + + return p +} + +func (p *Parser) nextToken() { + p.curToken = p.peekToken + p.peekToken = p.l.NextToken() +} + +func (p *Parser) curTokenIs(t token.TokenType) bool { + return p.curToken.Type == t +} + +func (p *Parser) peekTokenIs(t token.TokenType) bool { + return p.peekToken.Type == t +} + +func (p *Parser) expectPeek(t token.TokenType) bool { + if p.peekTokenIs(t) { + p.nextToken() + return true + } else { + p.peekError(t) + return false + } +} + +func (p *Parser) Errors() []string { + return p.errors +} + +func (p *Parser) peekError(t token.TokenType) { + msg := fmt.Sprintf("expected next token to be %s, got %s instead", + t, p.peekToken.Type) + p.errors = append(p.errors, msg) +} + +func (p *Parser) noPrefixParseFnError(t token.TokenType) { + msg := fmt.Sprintf("no prefix parse function for %s found", t) + p.errors = append(p.errors, msg) +} + +func (p *Parser) ParseProgram() *ast.Program { + program := &ast.Program{} + program.Statements = []ast.Statement{} + + for !p.curTokenIs(token.EOF) { + stmt := p.parseStatement() + if stmt != nil { + program.Statements = append(program.Statements, stmt) + } + p.nextToken() + } + + return program +} + +func (p *Parser) parseStatement() ast.Statement { + switch p.curToken.Type { + case token.LET: + return p.parseLetStatement() + case token.RETURN: + return p.parseReturnStatement() + default: + return p.parseExpressionStatement() + } +} + +func (p *Parser) parseLetStatement() *ast.LetStatement { + stmt := &ast.LetStatement{Token: p.curToken} + + if !p.expectPeek(token.IDENT) { + return nil + } + + stmt.Name = &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + + if !p.expectPeek(token.ASSIGN) { + return nil + } + + p.nextToken() + + stmt.Value = p.parseExpression(LOWEST) + + if p.peekTokenIs(token.SEMICOLON) { + p.nextToken() + } + + return stmt +} + +func (p *Parser) parseReturnStatement() *ast.ReturnStatement { + stmt := &ast.ReturnStatement{Token: p.curToken} + + p.nextToken() + + stmt.ReturnValue = p.parseExpression(LOWEST) + + if p.peekTokenIs(token.SEMICOLON) { + p.nextToken() + } + + return stmt +} + +func (p *Parser) parseExpressionStatement() *ast.ExpressionStatement { + stmt := &ast.ExpressionStatement{Token: p.curToken} + + stmt.Expression = p.parseExpression(LOWEST) + + if p.peekTokenIs(token.SEMICOLON) { + p.nextToken() + } + + return stmt +} + +func (p *Parser) parseExpression(precedence int) ast.Expression { + prefix := p.prefixParseFns[p.curToken.Type] + if prefix == nil { + p.noPrefixParseFnError(p.curToken.Type) + return nil + } + leftExp := prefix() + + for !p.peekTokenIs(token.SEMICOLON) && precedence < p.peekPrecedence() { + infix := p.infixParseFns[p.peekToken.Type] + if infix == nil { + return leftExp + } + + p.nextToken() + + leftExp = infix(leftExp) + } + + return leftExp +} + +func (p *Parser) peekPrecedence() int { + if p, ok := precedences[p.peekToken.Type]; ok { + return p + } + + return LOWEST +} + +func (p *Parser) curPrecedence() int { + if p, ok := precedences[p.curToken.Type]; ok { + return p + } + + return LOWEST +} + +func (p *Parser) parseIdentifier() ast.Expression { + return &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} +} + +func (p *Parser) parseIntegerLiteral() ast.Expression { + lit := &ast.IntegerLiteral{Token: p.curToken} + + value, err := strconv.ParseInt(p.curToken.Literal, 0, 64) + if err != nil { + msg := fmt.Sprintf("could not parse %q as integer", p.curToken.Literal) + p.errors = append(p.errors, msg) + return nil + } + + lit.Value = value + + return lit +} + +func (p *Parser) parseStringLiteral() ast.Expression { + return &ast.StringLiteral{Token: p.curToken, Value: p.curToken.Literal} +} + +func (p *Parser) parsePrefixExpression() ast.Expression { + expression := &ast.PrefixExpression{ + Token: p.curToken, + Operator: p.curToken.Literal, + } + + p.nextToken() + + expression.Right = p.parseExpression(PREFIX) + + return expression +} + +func (p *Parser) parseInfixExpression(left ast.Expression) ast.Expression { + expression := &ast.InfixExpression{ + Token: p.curToken, + Operator: p.curToken.Literal, + Left: left, + } + + precedence := p.curPrecedence() + p.nextToken() + expression.Right = p.parseExpression(precedence) + + return expression +} + +func (p *Parser) parseBoolean() ast.Expression { + return &ast.Boolean{Token: p.curToken, Value: p.curTokenIs(token.TRUE)} +} + +func (p *Parser) parseGroupedExpression() ast.Expression { + p.nextToken() + + exp := p.parseExpression(LOWEST) + + if !p.expectPeek(token.RPAREN) { + return nil + } + + return exp +} + +func (p *Parser) parseIfExpression() ast.Expression { + expression := &ast.IfExpression{Token: p.curToken} + + if !p.expectPeek(token.LPAREN) { + return nil + } + + p.nextToken() + expression.Condition = p.parseExpression(LOWEST) + + if !p.expectPeek(token.RPAREN) { + return nil + } + + if !p.expectPeek(token.LBRACE) { + return nil + } + + expression.Consequence = p.parseBlockStatement() + + if p.peekTokenIs(token.ELSE) { + p.nextToken() + + if !p.expectPeek(token.LBRACE) { + return nil + } + + expression.Alternative = p.parseBlockStatement() + } + + return expression +} + +func (p *Parser) parseBlockStatement() *ast.BlockStatement { + block := &ast.BlockStatement{Token: p.curToken} + block.Statements = []ast.Statement{} + + p.nextToken() + + for !p.curTokenIs(token.RBRACE) && !p.curTokenIs(token.EOF) { + stmt := p.parseStatement() + if stmt != nil { + block.Statements = append(block.Statements, stmt) + } + p.nextToken() + } + + return block +} + +func (p *Parser) parseFunctionLiteral() ast.Expression { + lit := &ast.FunctionLiteral{Token: p.curToken} + + if !p.expectPeek(token.LPAREN) { + return nil + } + + lit.Parameters = p.parseFunctionParameters() + + if !p.expectPeek(token.LBRACE) { + return nil + } + + lit.Body = p.parseBlockStatement() + + return lit +} + +func (p *Parser) parseFunctionParameters() []*ast.Identifier { + identifiers := []*ast.Identifier{} + + if p.peekTokenIs(token.RPAREN) { + p.nextToken() + return identifiers + } + + p.nextToken() + + ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + identifiers = append(identifiers, ident) + + for p.peekTokenIs(token.COMMA) { + p.nextToken() + p.nextToken() + ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + identifiers = append(identifiers, ident) + } + + if !p.expectPeek(token.RPAREN) { + return nil + } + + return identifiers +} + +func (p *Parser) parseCallExpression(function ast.Expression) ast.Expression { + exp := &ast.CallExpression{Token: p.curToken, Function: function} + exp.Arguments = p.parseExpressionList(token.RPAREN) + return exp +} + +func (p *Parser) parseExpressionList(end token.TokenType) []ast.Expression { + list := []ast.Expression{} + + if p.peekTokenIs(end) { + p.nextToken() + return list + } + + p.nextToken() + list = append(list, p.parseExpression(LOWEST)) + + for p.peekTokenIs(token.COMMA) { + p.nextToken() + p.nextToken() + list = append(list, p.parseExpression(LOWEST)) + } + + if !p.expectPeek(end) { + return nil + } + + return list +} + +func (p *Parser) parseArrayLiteral() ast.Expression { + array := &ast.ArrayLiteral{Token: p.curToken} + + array.Elements = p.parseExpressionList(token.RBRACKET) + + return array +} + +func (p *Parser) parseIndexExpression(left ast.Expression) ast.Expression { + exp := &ast.IndexExpression{Token: p.curToken, Left: left} + + p.nextToken() + exp.Index = p.parseExpression(LOWEST) + + if !p.expectPeek(token.RBRACKET) { + return nil + } + + return exp +} + +func (p *Parser) parseHashLiteral() ast.Expression { + hash := &ast.HashLiteral{Token: p.curToken} + hash.Pairs = make(map[ast.Expression]ast.Expression) + + for !p.peekTokenIs(token.RBRACE) { + p.nextToken() + key := p.parseExpression(LOWEST) + + if !p.expectPeek(token.COLON) { + return nil + } + + p.nextToken() + value := p.parseExpression(LOWEST) + + hash.Pairs[key] = value + + if !p.peekTokenIs(token.RBRACE) && !p.expectPeek(token.COMMA) { + return nil + } + } + + if !p.expectPeek(token.RBRACE) { + return nil + } + + return hash +} + +func (p *Parser) registerPrefix(tokenType token.TokenType, fn prefixParseFn) { + p.prefixParseFns[tokenType] = fn +} + +func (p *Parser) registerInfix(tokenType token.TokenType, fn infixParseFn) { + p.infixParseFns[tokenType] = fn +} diff --git a/wcig_code_1_2/03/src/monkey/parser/parser_test.go b/wcig_code_1_2/03/src/monkey/parser/parser_test.go new file mode 100644 index 0000000..674487d --- /dev/null +++ b/wcig_code_1_2/03/src/monkey/parser/parser_test.go @@ -0,0 +1,1083 @@ +package parser + +import ( + "fmt" + "monkey/ast" + "monkey/lexer" + "testing" +) + +func TestLetStatements(t *testing.T) { + tests := []struct { + input string + expectedIdentifier string + expectedValue interface{} + }{ + {"let x = 5;", "x", 5}, + {"let y = true;", "y", true}, + {"let foobar = y;", "foobar", "y"}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain 1 statements. got=%d", + len(program.Statements)) + } + + stmt := program.Statements[0] + if !testLetStatement(t, stmt, tt.expectedIdentifier) { + return + } + + val := stmt.(*ast.LetStatement).Value + if !testLiteralExpression(t, val, tt.expectedValue) { + return + } + } +} + +func TestReturnStatements(t *testing.T) { + tests := []struct { + input string + expectedValue interface{} + }{ + {"return 5;", 5}, + {"return true;", true}, + {"return foobar;", "foobar"}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain 1 statements. got=%d", + len(program.Statements)) + } + + stmt := program.Statements[0] + returnStmt, ok := stmt.(*ast.ReturnStatement) + if !ok { + t.Fatalf("stmt not *ast.returnStatement. got=%T", stmt) + } + if returnStmt.TokenLiteral() != "return" { + t.Fatalf("returnStmt.TokenLiteral not 'return', got %q", + returnStmt.TokenLiteral()) + } + if testLiteralExpression(t, returnStmt.ReturnValue, tt.expectedValue) { + return + } + } +} + +func TestIdentifierExpression(t *testing.T) { + input := "foobar;" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program has not enough statements. got=%d", + len(program.Statements)) + } + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + ident, ok := stmt.Expression.(*ast.Identifier) + if !ok { + t.Fatalf("exp not *ast.Identifier. got=%T", stmt.Expression) + } + if ident.Value != "foobar" { + t.Errorf("ident.Value not %s. got=%s", "foobar", ident.Value) + } + if ident.TokenLiteral() != "foobar" { + t.Errorf("ident.TokenLiteral not %s. got=%s", "foobar", + ident.TokenLiteral()) + } +} + +func TestIntegerLiteralExpression(t *testing.T) { + input := "5;" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program has not enough statements. got=%d", + len(program.Statements)) + } + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + literal, ok := stmt.Expression.(*ast.IntegerLiteral) + if !ok { + t.Fatalf("exp not *ast.IntegerLiteral. got=%T", stmt.Expression) + } + if literal.Value != 5 { + t.Errorf("literal.Value not %d. got=%d", 5, literal.Value) + } + if literal.TokenLiteral() != "5" { + t.Errorf("literal.TokenLiteral not %s. got=%s", "5", + literal.TokenLiteral()) + } +} + +func TestParsingPrefixExpressions(t *testing.T) { + prefixTests := []struct { + input string + operator string + value interface{} + }{ + {"!5;", "!", 5}, + {"-15;", "-", 15}, + {"!foobar;", "!", "foobar"}, + {"-foobar;", "-", "foobar"}, + {"!true;", "!", true}, + {"!false;", "!", false}, + } + + for _, tt := range prefixTests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.PrefixExpression) + if !ok { + t.Fatalf("stmt is not ast.PrefixExpression. got=%T", stmt.Expression) + } + if exp.Operator != tt.operator { + t.Fatalf("exp.Operator is not '%s'. got=%s", + tt.operator, exp.Operator) + } + if !testLiteralExpression(t, exp.Right, tt.value) { + return + } + } +} + +func TestParsingInfixExpressions(t *testing.T) { + infixTests := []struct { + input string + leftValue interface{} + operator string + rightValue interface{} + }{ + {"5 + 5;", 5, "+", 5}, + {"5 - 5;", 5, "-", 5}, + {"5 * 5;", 5, "*", 5}, + {"5 / 5;", 5, "/", 5}, + {"5 > 5;", 5, ">", 5}, + {"5 < 5;", 5, "<", 5}, + {"5 == 5;", 5, "==", 5}, + {"5 != 5;", 5, "!=", 5}, + {"foobar + barfoo;", "foobar", "+", "barfoo"}, + {"foobar - barfoo;", "foobar", "-", "barfoo"}, + {"foobar * barfoo;", "foobar", "*", "barfoo"}, + {"foobar / barfoo;", "foobar", "/", "barfoo"}, + {"foobar > barfoo;", "foobar", ">", "barfoo"}, + {"foobar < barfoo;", "foobar", "<", "barfoo"}, + {"foobar == barfoo;", "foobar", "==", "barfoo"}, + {"foobar != barfoo;", "foobar", "!=", "barfoo"}, + {"true == true", true, "==", true}, + {"true != false", true, "!=", false}, + {"false == false", false, "==", false}, + } + + for _, tt := range infixTests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + if !testInfixExpression(t, stmt.Expression, tt.leftValue, + tt.operator, tt.rightValue) { + return + } + } +} + +func TestOperatorPrecedenceParsing(t *testing.T) { + tests := []struct { + input string + expected string + }{ + { + "-a * b", + "((-a) * b)", + }, + { + "!-a", + "(!(-a))", + }, + { + "a + b + c", + "((a + b) + c)", + }, + { + "a + b - c", + "((a + b) - c)", + }, + { + "a * b * c", + "((a * b) * c)", + }, + { + "a * b / c", + "((a * b) / c)", + }, + { + "a + b / c", + "(a + (b / c))", + }, + { + "a + b * c + d / e - f", + "(((a + (b * c)) + (d / e)) - f)", + }, + { + "3 + 4; -5 * 5", + "(3 + 4)((-5) * 5)", + }, + { + "5 > 4 == 3 < 4", + "((5 > 4) == (3 < 4))", + }, + { + "5 < 4 != 3 > 4", + "((5 < 4) != (3 > 4))", + }, + { + "3 + 4 * 5 == 3 * 1 + 4 * 5", + "((3 + (4 * 5)) == ((3 * 1) + (4 * 5)))", + }, + { + "true", + "true", + }, + { + "false", + "false", + }, + { + "3 > 5 == false", + "((3 > 5) == false)", + }, + { + "3 < 5 == true", + "((3 < 5) == true)", + }, + { + "1 + (2 + 3) + 4", + "((1 + (2 + 3)) + 4)", + }, + { + "(5 + 5) * 2", + "((5 + 5) * 2)", + }, + { + "2 / (5 + 5)", + "(2 / (5 + 5))", + }, + { + "(5 + 5) * 2 * (5 + 5)", + "(((5 + 5) * 2) * (5 + 5))", + }, + { + "-(5 + 5)", + "(-(5 + 5))", + }, + { + "!(true == true)", + "(!(true == true))", + }, + { + "a + add(b * c) + d", + "((a + add((b * c))) + d)", + }, + { + "add(a, b, 1, 2 * 3, 4 + 5, add(6, 7 * 8))", + "add(a, b, 1, (2 * 3), (4 + 5), add(6, (7 * 8)))", + }, + { + "add(a + b + c * d / f + g)", + "add((((a + b) + ((c * d) / f)) + g))", + }, + { + "a * [1, 2, 3, 4][b * c] * d", + "((a * ([1, 2, 3, 4][(b * c)])) * d)", + }, + { + "add(a * b[2], b[1], 2 * [1, 2][1])", + "add((a * (b[2])), (b[1]), (2 * ([1, 2][1])))", + }, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + actual := program.String() + if actual != tt.expected { + t.Errorf("expected=%q, got=%q", tt.expected, actual) + } + } +} + +func TestBooleanExpression(t *testing.T) { + tests := []struct { + input string + expectedBoolean bool + }{ + {"true;", true}, + {"false;", false}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program has not enough statements. got=%d", + len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + boolean, ok := stmt.Expression.(*ast.Boolean) + if !ok { + t.Fatalf("exp not *ast.Boolean. got=%T", stmt.Expression) + } + if boolean.Value != tt.expectedBoolean { + t.Errorf("boolean.Value not %t. got=%t", tt.expectedBoolean, + boolean.Value) + } + } +} + +func TestIfExpression(t *testing.T) { + input := `if (x < y) { x }` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.IfExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.IfExpression. got=%T", + stmt.Expression) + } + + if !testInfixExpression(t, exp.Condition, "x", "<", "y") { + return + } + + if len(exp.Consequence.Statements) != 1 { + t.Errorf("consequence is not 1 statements. got=%d\n", + len(exp.Consequence.Statements)) + } + + consequence, ok := exp.Consequence.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", + exp.Consequence.Statements[0]) + } + + if !testIdentifier(t, consequence.Expression, "x") { + return + } + + if exp.Alternative != nil { + t.Errorf("exp.Alternative.Statements was not nil. got=%+v", exp.Alternative) + } +} + +func TestIfElseExpression(t *testing.T) { + input := `if (x < y) { x } else { y }` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.IfExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.IfExpression. got=%T", stmt.Expression) + } + + if !testInfixExpression(t, exp.Condition, "x", "<", "y") { + return + } + + if len(exp.Consequence.Statements) != 1 { + t.Errorf("consequence is not 1 statements. got=%d\n", + len(exp.Consequence.Statements)) + } + + consequence, ok := exp.Consequence.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", + exp.Consequence.Statements[0]) + } + + if !testIdentifier(t, consequence.Expression, "x") { + return + } + + if len(exp.Alternative.Statements) != 1 { + t.Errorf("exp.Alternative.Statements does not contain 1 statements. got=%d\n", + len(exp.Alternative.Statements)) + } + + alternative, ok := exp.Alternative.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", + exp.Alternative.Statements[0]) + } + + if !testIdentifier(t, alternative.Expression, "y") { + return + } +} + +func TestFunctionLiteralParsing(t *testing.T) { + input := `fn(x, y) { x + y; }` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + function, ok := stmt.Expression.(*ast.FunctionLiteral) + if !ok { + t.Fatalf("stmt.Expression is not ast.FunctionLiteral. got=%T", + stmt.Expression) + } + + if len(function.Parameters) != 2 { + t.Fatalf("function literal parameters wrong. want 2, got=%d\n", + len(function.Parameters)) + } + + testLiteralExpression(t, function.Parameters[0], "x") + testLiteralExpression(t, function.Parameters[1], "y") + + if len(function.Body.Statements) != 1 { + t.Fatalf("function.Body.Statements has not 1 statements. got=%d\n", + len(function.Body.Statements)) + } + + bodyStmt, ok := function.Body.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("function body stmt is not ast.ExpressionStatement. got=%T", + function.Body.Statements[0]) + } + + testInfixExpression(t, bodyStmt.Expression, "x", "+", "y") +} + +func TestFunctionParameterParsing(t *testing.T) { + tests := []struct { + input string + expectedParams []string + }{ + {input: "fn() {};", expectedParams: []string{}}, + {input: "fn(x) {};", expectedParams: []string{"x"}}, + {input: "fn(x, y, z) {};", expectedParams: []string{"x", "y", "z"}}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + function := stmt.Expression.(*ast.FunctionLiteral) + + if len(function.Parameters) != len(tt.expectedParams) { + t.Errorf("length parameters wrong. want %d, got=%d\n", + len(tt.expectedParams), len(function.Parameters)) + } + + for i, ident := range tt.expectedParams { + testLiteralExpression(t, function.Parameters[i], ident) + } + } +} + +func TestCallExpressionParsing(t *testing.T) { + input := "add(1, 2 * 3, 4 + 5);" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("stmt is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.CallExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.CallExpression. got=%T", + stmt.Expression) + } + + if !testIdentifier(t, exp.Function, "add") { + return + } + + if len(exp.Arguments) != 3 { + t.Fatalf("wrong length of arguments. got=%d", len(exp.Arguments)) + } + + testLiteralExpression(t, exp.Arguments[0], 1) + testInfixExpression(t, exp.Arguments[1], 2, "*", 3) + testInfixExpression(t, exp.Arguments[2], 4, "+", 5) +} + +func TestCallExpressionParameterParsing(t *testing.T) { + tests := []struct { + input string + expectedIdent string + expectedArgs []string + }{ + { + input: "add();", + expectedIdent: "add", + expectedArgs: []string{}, + }, + { + input: "add(1);", + expectedIdent: "add", + expectedArgs: []string{"1"}, + }, + { + input: "add(1, 2 * 3, 4 + 5);", + expectedIdent: "add", + expectedArgs: []string{"1", "(2 * 3)", "(4 + 5)"}, + }, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + exp, ok := stmt.Expression.(*ast.CallExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.CallExpression. got=%T", + stmt.Expression) + } + + if !testIdentifier(t, exp.Function, tt.expectedIdent) { + return + } + + if len(exp.Arguments) != len(tt.expectedArgs) { + t.Fatalf("wrong number of arguments. want=%d, got=%d", + len(tt.expectedArgs), len(exp.Arguments)) + } + + for i, arg := range tt.expectedArgs { + if exp.Arguments[i].String() != arg { + t.Errorf("argument %d wrong. want=%q, got=%q", i, + arg, exp.Arguments[i].String()) + } + } + } +} + +func TestStringLiteralExpression(t *testing.T) { + input := `"hello world";` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + literal, ok := stmt.Expression.(*ast.StringLiteral) + if !ok { + t.Fatalf("exp not *ast.StringLiteral. got=%T", stmt.Expression) + } + + if literal.Value != "hello world" { + t.Errorf("literal.Value not %q. got=%q", "hello world", literal.Value) + } +} + +func TestParsingEmptyArrayLiterals(t *testing.T) { + input := "[]" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + array, ok := stmt.Expression.(*ast.ArrayLiteral) + if !ok { + t.Fatalf("exp not ast.ArrayLiteral. got=%T", stmt.Expression) + } + + if len(array.Elements) != 0 { + t.Errorf("len(array.Elements) not 0. got=%d", len(array.Elements)) + } +} + +func TestParsingArrayLiterals(t *testing.T) { + input := "[1, 2 * 2, 3 + 3]" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + array, ok := stmt.Expression.(*ast.ArrayLiteral) + if !ok { + t.Fatalf("exp not ast.ArrayLiteral. got=%T", stmt.Expression) + } + + if len(array.Elements) != 3 { + t.Fatalf("len(array.Elements) not 3. got=%d", len(array.Elements)) + } + + testIntegerLiteral(t, array.Elements[0], 1) + testInfixExpression(t, array.Elements[1], 2, "*", 2) + testInfixExpression(t, array.Elements[2], 3, "+", 3) +} + +func TestParsingIndexExpressions(t *testing.T) { + input := "myArray[1 + 1]" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + indexExp, ok := stmt.Expression.(*ast.IndexExpression) + if !ok { + t.Fatalf("exp not *ast.IndexExpression. got=%T", stmt.Expression) + } + + if !testIdentifier(t, indexExp.Left, "myArray") { + return + } + + if !testInfixExpression(t, indexExp.Index, 1, "+", 1) { + return + } +} + +func TestParsingEmptyHashLiteral(t *testing.T) { + input := "{}" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + if len(hash.Pairs) != 0 { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } +} + +func TestParsingHashLiteralsStringKeys(t *testing.T) { + input := `{"one": 1, "two": 2, "three": 3}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + expected := map[string]int64{ + "one": 1, + "two": 2, + "three": 3, + } + + if len(hash.Pairs) != len(expected) { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } + + for key, value := range hash.Pairs { + literal, ok := key.(*ast.StringLiteral) + if !ok { + t.Errorf("key is not ast.StringLiteral. got=%T", key) + continue + } + + expectedValue := expected[literal.String()] + testIntegerLiteral(t, value, expectedValue) + } +} + +func TestParsingHashLiteralsBooleanKeys(t *testing.T) { + input := `{true: 1, false: 2}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + expected := map[string]int64{ + "true": 1, + "false": 2, + } + + if len(hash.Pairs) != len(expected) { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } + + for key, value := range hash.Pairs { + boolean, ok := key.(*ast.Boolean) + if !ok { + t.Errorf("key is not ast.BooleanLiteral. got=%T", key) + continue + } + + expectedValue := expected[boolean.String()] + testIntegerLiteral(t, value, expectedValue) + } +} + +func TestParsingHashLiteralsIntegerKeys(t *testing.T) { + input := `{1: 1, 2: 2, 3: 3}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + expected := map[string]int64{ + "1": 1, + "2": 2, + "3": 3, + } + + if len(hash.Pairs) != len(expected) { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } + + for key, value := range hash.Pairs { + integer, ok := key.(*ast.IntegerLiteral) + if !ok { + t.Errorf("key is not ast.IntegerLiteral. got=%T", key) + continue + } + + expectedValue := expected[integer.String()] + + testIntegerLiteral(t, value, expectedValue) + } +} + +func TestParsingHashLiteralsWithExpressions(t *testing.T) { + input := `{"one": 0 + 1, "two": 10 - 8, "three": 15 / 5}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + if len(hash.Pairs) != 3 { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } + + tests := map[string]func(ast.Expression){ + "one": func(e ast.Expression) { + testInfixExpression(t, e, 0, "+", 1) + }, + "two": func(e ast.Expression) { + testInfixExpression(t, e, 10, "-", 8) + }, + "three": func(e ast.Expression) { + testInfixExpression(t, e, 15, "/", 5) + }, + } + + for key, value := range hash.Pairs { + literal, ok := key.(*ast.StringLiteral) + if !ok { + t.Errorf("key is not ast.StringLiteral. got=%T", key) + continue + } + + testFunc, ok := tests[literal.String()] + if !ok { + t.Errorf("No test function for key %q found", literal.String()) + continue + } + + testFunc(value) + } +} + +func testLetStatement(t *testing.T, s ast.Statement, name string) bool { + if s.TokenLiteral() != "let" { + t.Errorf("s.TokenLiteral not 'let'. got=%q", s.TokenLiteral()) + return false + } + + letStmt, ok := s.(*ast.LetStatement) + if !ok { + t.Errorf("s not *ast.LetStatement. got=%T", s) + return false + } + + if letStmt.Name.Value != name { + t.Errorf("letStmt.Name.Value not '%s'. got=%s", name, letStmt.Name.Value) + return false + } + + if letStmt.Name.TokenLiteral() != name { + t.Errorf("letStmt.Name.TokenLiteral() not '%s'. got=%s", + name, letStmt.Name.TokenLiteral()) + return false + } + + return true +} + +func testInfixExpression(t *testing.T, exp ast.Expression, left interface{}, + operator string, right interface{}) bool { + + opExp, ok := exp.(*ast.InfixExpression) + if !ok { + t.Errorf("exp is not ast.InfixExpression. got=%T(%s)", exp, exp) + return false + } + + if !testLiteralExpression(t, opExp.Left, left) { + return false + } + + if opExp.Operator != operator { + t.Errorf("exp.Operator is not '%s'. got=%q", operator, opExp.Operator) + return false + } + + if !testLiteralExpression(t, opExp.Right, right) { + return false + } + + return true +} + +func testLiteralExpression( + t *testing.T, + exp ast.Expression, + expected interface{}, +) bool { + switch v := expected.(type) { + case int: + return testIntegerLiteral(t, exp, int64(v)) + case int64: + return testIntegerLiteral(t, exp, v) + case string: + return testIdentifier(t, exp, v) + case bool: + return testBooleanLiteral(t, exp, v) + } + t.Errorf("type of exp not handled. got=%T", exp) + return false +} + +func testIntegerLiteral(t *testing.T, il ast.Expression, value int64) bool { + integ, ok := il.(*ast.IntegerLiteral) + if !ok { + t.Errorf("il not *ast.IntegerLiteral. got=%T", il) + return false + } + + if integ.Value != value { + t.Errorf("integ.Value not %d. got=%d", value, integ.Value) + return false + } + + if integ.TokenLiteral() != fmt.Sprintf("%d", value) { + t.Errorf("integ.TokenLiteral not %d. got=%s", value, + integ.TokenLiteral()) + return false + } + + return true +} + +func testIdentifier(t *testing.T, exp ast.Expression, value string) bool { + ident, ok := exp.(*ast.Identifier) + if !ok { + t.Errorf("exp not *ast.Identifier. got=%T", exp) + return false + } + + if ident.Value != value { + t.Errorf("ident.Value not %s. got=%s", value, ident.Value) + return false + } + + if ident.TokenLiteral() != value { + t.Errorf("ident.TokenLiteral not %s. got=%s", value, + ident.TokenLiteral()) + return false + } + + return true +} + +func testBooleanLiteral(t *testing.T, exp ast.Expression, value bool) bool { + bo, ok := exp.(*ast.Boolean) + if !ok { + t.Errorf("exp not *ast.Boolean. got=%T", exp) + return false + } + + if bo.Value != value { + t.Errorf("bo.Value not %t. got=%t", value, bo.Value) + return false + } + + if bo.TokenLiteral() != fmt.Sprintf("%t", value) { + t.Errorf("bo.TokenLiteral not %t. got=%s", + value, bo.TokenLiteral()) + return false + } + + return true +} + +func checkParserErrors(t *testing.T, p *Parser) { + errors := p.Errors() + if len(errors) == 0 { + return + } + + t.Errorf("parser has %d errors", len(errors)) + for _, msg := range errors { + t.Errorf("parser error: %q", msg) + } + t.FailNow() +} diff --git a/wcig_code_1_2/03/src/monkey/parser/parser_tracing.go b/wcig_code_1_2/03/src/monkey/parser/parser_tracing.go new file mode 100644 index 0000000..5fc569b --- /dev/null +++ b/wcig_code_1_2/03/src/monkey/parser/parser_tracing.go @@ -0,0 +1,32 @@ +package parser + +import ( + "fmt" + "strings" +) + +var traceLevel int = 0 + +const traceIdentPlaceholder string = "\t" + +func identLevel() string { + return strings.Repeat(traceIdentPlaceholder, traceLevel-1) +} + +func tracePrint(fs string) { + fmt.Printf("%s%s\n", identLevel(), fs) +} + +func incIdent() { traceLevel = traceLevel + 1 } +func decIdent() { traceLevel = traceLevel - 1 } + +func trace(msg string) string { + incIdent() + tracePrint("BEGIN " + msg) + return msg +} + +func untrace(msg string) { + tracePrint("END " + msg) + decIdent() +} diff --git a/wcig_code_1_2/03/src/monkey/repl/repl.go b/wcig_code_1_2/03/src/monkey/repl/repl.go new file mode 100644 index 0000000..b5fbe0e --- /dev/null +++ b/wcig_code_1_2/03/src/monkey/repl/repl.go @@ -0,0 +1,75 @@ +package repl + +import ( + "bufio" + "fmt" + "io" + "monkey/compiler" + "monkey/lexer" + "monkey/parser" + "monkey/vm" +) + +const PROMPT = ">> " + +func Start(in io.Reader, out io.Writer) { + scanner := bufio.NewScanner(in) + + for { + fmt.Fprintf(out, PROMPT) + scanned := scanner.Scan() + if !scanned { + return + } + + line := scanner.Text() + l := lexer.New(line) + p := parser.New(l) + + program := p.ParseProgram() + if len(p.Errors()) != 0 { + printParserErrors(out, p.Errors()) + continue + } + + comp := compiler.New() + err := comp.Compile(program) + if err != nil { + fmt.Fprintf(out, "Woops! Compilation failed:\n %s\n", err) + continue + } + + machine := vm.New(comp.Bytecode()) + err = machine.Run() + if err != nil { + fmt.Fprintf(out, "Woops! Executing bytecode failed:\n %s\n", err) + continue + } + + lastPopped := machine.LastPoppedStackElem() + io.WriteString(out, lastPopped.Inspect()) + io.WriteString(out, "\n") + } +} + +const MONKEY_FACE = ` __,__ + .--. .-" "-. .--. + / .. \/ .-. .-. \/ .. \ + | | '| / Y \ |' | | + | \ \ \ 0 | 0 / / / | + \ '- ,\.-"""""""-./, -' / + ''-' /_ ^ ^ _\ '-'' + | \._ _./ | + \ \ '~' / / + '._ '-=-' _.' + '-----' +` + +func printParserErrors(out io.Writer, errors []string) { + io.WriteString(out, MONKEY_FACE) + io.WriteString(out, "Woops! We ran into some monkey business here!\n") + io.WriteString(out, " parser errors:\n") + for _, msg := range errors { + io.WriteString(out, "\t"+msg+"\n") + } +} diff --git a/wcig_code_1_2/03/src/monkey/token/token.go b/wcig_code_1_2/03/src/monkey/token/token.go new file mode 100644 index 0000000..3d2d2f7 --- /dev/null +++ b/wcig_code_1_2/03/src/monkey/token/token.go @@ -0,0 +1,70 @@ +package token + +type TokenType string + +const ( + ILLEGAL = "ILLEGAL" + EOF = "EOF" + + // Identifiers + literals + IDENT = "IDENT" // add, foobar, x, y, ... + INT = "INT" // 1343456 + STRING = "STRING" // "foobar" + + // Operators + ASSIGN = "=" + PLUS = "+" + MINUS = "-" + BANG = "!" + ASTERISK = "*" + SLASH = "/" + + LT = "<" + GT = ">" + + EQ = "==" + NOT_EQ = "!=" + + // Delimiters + COMMA = "," + SEMICOLON = ";" + COLON = ":" + + LPAREN = "(" + RPAREN = ")" + LBRACE = "{" + RBRACE = "}" + LBRACKET = "[" + RBRACKET = "]" + + // Keywords + FUNCTION = "FUNCTION" + LET = "LET" + TRUE = "TRUE" + FALSE = "FALSE" + IF = "IF" + ELSE = "ELSE" + RETURN = "RETURN" +) + +type Token struct { + Type TokenType + Literal string +} + +var keywords = map[string]TokenType{ + "fn": FUNCTION, + "let": LET, + "true": TRUE, + "false": FALSE, + "if": IF, + "else": ELSE, + "return": RETURN, +} + +func LookupIdent(ident string) TokenType { + if tok, ok := keywords[ident]; ok { + return tok + } + return IDENT +} diff --git a/wcig_code_1_2/03/src/monkey/vm/vm.go b/wcig_code_1_2/03/src/monkey/vm/vm.go new file mode 100644 index 0000000..e80eb19 --- /dev/null +++ b/wcig_code_1_2/03/src/monkey/vm/vm.go @@ -0,0 +1,219 @@ +package vm + +import ( + "fmt" + "monkey/code" + "monkey/compiler" + "monkey/object" +) + +const StackSize = 2048 + +var True = &object.Boolean{Value: true} +var False = &object.Boolean{Value: false} + +type VM struct { + constants []object.Object + instructions code.Instructions + + stack []object.Object + sp int // Always points to the next value. Top of stack is stack[sp-1] +} + +func New(bytecode *compiler.Bytecode) *VM { + return &VM{ + instructions: bytecode.Instructions, + constants: bytecode.Constants, + + stack: make([]object.Object, StackSize), + sp: 0, + } +} + +func (vm *VM) LastPoppedStackElem() object.Object { + return vm.stack[vm.sp] +} + +func (vm *VM) Run() error { + for ip := 0; ip < len(vm.instructions); ip++ { + op := code.Opcode(vm.instructions[ip]) + + switch op { + case code.OpConstant: + constIndex := code.ReadUint16(vm.instructions[ip+1:]) + ip += 2 + + err := vm.push(vm.constants[constIndex]) + if err != nil { + return err + } + + case code.OpPop: + vm.pop() + + case code.OpAdd, code.OpSub, code.OpMul, code.OpDiv: + err := vm.executeBinaryOperation(op) + if err != nil { + return err + } + + case code.OpTrue: + err := vm.push(True) + if err != nil { + return err + } + + case code.OpFalse: + err := vm.push(False) + if err != nil { + return err + } + + case code.OpEqual, code.OpNotEqual, code.OpGreaterThan: + err := vm.executeComparison(op) + if err != nil { + return err + } + + case code.OpBang: + err := vm.executeBangOperator() + if err != nil { + return err + } + + case code.OpMinus: + err := vm.executeMinusOperator() + if err != nil { + return err + } + } + } + + return nil +} + +func (vm *VM) push(o object.Object) error { + if vm.sp >= StackSize { + return fmt.Errorf("stack overflow") + } + + vm.stack[vm.sp] = o + vm.sp++ + + return nil +} + +func (vm *VM) pop() object.Object { + o := vm.stack[vm.sp-1] + vm.sp-- + return o +} + +func (vm *VM) executeBinaryOperation(op code.Opcode) error { + right := vm.pop() + left := vm.pop() + + leftType := left.Type() + rightType := right.Type() + + if leftType == object.INTEGER_OBJ && rightType == object.INTEGER_OBJ { + return vm.executeBinaryIntegerOperation(op, left, right) + } + + return fmt.Errorf("unsupported types for binary operation: %s %s", + leftType, rightType) +} + +func (vm *VM) executeBinaryIntegerOperation( + op code.Opcode, + left, right object.Object, +) error { + leftValue := left.(*object.Integer).Value + rightValue := right.(*object.Integer).Value + + var result int64 + + switch op { + case code.OpAdd: + result = leftValue + rightValue + case code.OpSub: + result = leftValue - rightValue + case code.OpMul: + result = leftValue * rightValue + case code.OpDiv: + result = leftValue / rightValue + default: + return fmt.Errorf("unknown integer operator: %d", op) + } + + return vm.push(&object.Integer{Value: result}) +} + +func (vm *VM) executeComparison(op code.Opcode) error { + right := vm.pop() + left := vm.pop() + + if left.Type() == object.INTEGER_OBJ && right.Type() == object.INTEGER_OBJ { + return vm.executeIntegerComparison(op, left, right) + } + + switch op { + case code.OpEqual: + return vm.push(nativeBoolToBooleanObject(right == left)) + case code.OpNotEqual: + return vm.push(nativeBoolToBooleanObject(right != left)) + default: + return fmt.Errorf("unknown operator: %d (%s %s)", + op, left.Type(), right.Type()) + } +} + +func (vm *VM) executeIntegerComparison( + op code.Opcode, + left, right object.Object, +) error { + leftValue := left.(*object.Integer).Value + rightValue := right.(*object.Integer).Value + + switch op { + case code.OpEqual: + return vm.push(nativeBoolToBooleanObject(rightValue == leftValue)) + case code.OpNotEqual: + return vm.push(nativeBoolToBooleanObject(rightValue != leftValue)) + case code.OpGreaterThan: + return vm.push(nativeBoolToBooleanObject(leftValue > rightValue)) + default: + return fmt.Errorf("unknown operator: %d", op) + } +} + +func (vm *VM) executeBangOperator() error { + operand := vm.pop() + + switch operand { + case True: + return vm.push(False) + case False: + return vm.push(True) + default: + return vm.push(False) + } +} + +func (vm *VM) executeMinusOperator() error { + operand := vm.pop() + + if operand.Type() != object.INTEGER_OBJ { + return fmt.Errorf("unsupported type for negation: %s", operand.Type()) + } + + value := operand.(*object.Integer).Value + return vm.push(&object.Integer{Value: -value}) +} + +func nativeBoolToBooleanObject(input bool) *object.Boolean { + if input { + return True + } + return False +} diff --git a/wcig_code_1_2/03/src/monkey/vm/vm_test.go b/wcig_code_1_2/03/src/monkey/vm/vm_test.go new file mode 100644 index 0000000..2996e88 --- /dev/null +++ b/wcig_code_1_2/03/src/monkey/vm/vm_test.go @@ -0,0 +1,154 @@ +package vm + +import ( + "fmt" + "monkey/ast" + "monkey/compiler" + "monkey/lexer" + "monkey/object" + "monkey/parser" + "testing" +) + +func TestIntegerArithmetic(t *testing.T) { + tests := []vmTestCase{ + {"1", 1}, + {"2", 2}, + {"1 + 2", 3}, + {"1 - 2", -1}, + {"1 * 2", 2}, + {"4 / 2", 2}, + {"50 / 2 * 2 + 10 - 5", 55}, + {"5 * (2 + 10)", 60}, + {"5 + 5 + 5 + 5 - 10", 10}, + {"2 * 2 * 2 * 2 * 2", 32}, + {"5 * 2 + 10", 20}, + {"5 + 2 * 10", 25}, + {"5 * (2 + 10)", 60}, + {"-5", -5}, + {"-10", -10}, + {"-50 + 100 + -50", 0}, + {"(5 + 10 * 2 + 15 / 3) * 2 + -10", 50}, + } + + runVmTests(t, tests) +} + +func TestBooleanExpressions(t *testing.T) { + tests := []vmTestCase{ + {"true", true}, + {"false", false}, + {"1 < 2", true}, + {"1 > 2", false}, + {"1 < 1", false}, + {"1 > 1", false}, + {"1 == 1", true}, + {"1 != 1", false}, + {"1 == 2", false}, + {"1 != 2", true}, + {"true == true", true}, + {"false == false", true}, + {"true == false", false}, + {"true != false", true}, + {"false != true", true}, + {"(1 < 2) == true", true}, + {"(1 < 2) == false", false}, + {"(1 > 2) == true", false}, + {"(1 > 2) == false", true}, + {"!true", false}, + {"!false", true}, + {"!5", false}, + {"!!true", true}, + {"!!false", false}, + {"!!5", true}, + } + + runVmTests(t, tests) +} + +type vmTestCase struct { + input string + expected interface{} +} + +func runVmTests(t *testing.T, tests []vmTestCase) { + t.Helper() + + for _, tt := range tests { + program := parse(tt.input) + + comp := compiler.New() + err := comp.Compile(program) + if err != nil { + t.Fatalf("compiler error: %s", err) + } + + vm := New(comp.Bytecode()) + err = vm.Run() + if err != nil { + t.Fatalf("vm error: %s", err) + } + + stackElem := vm.LastPoppedStackElem() + + testExpectedObject(t, tt.expected, stackElem) + } +} + +func parse(input string) *ast.Program { + l := lexer.New(input) + p := parser.New(l) + return p.ParseProgram() +} + +func testExpectedObject( + t *testing.T, + expected interface{}, + actual object.Object, +) { + t.Helper() + + switch expected := expected.(type) { + case int: + err := testIntegerObject(int64(expected), actual) + if err != nil { + t.Errorf("testIntegerObject failed: %s", err) + } + + case bool: + err := testBooleanObject(bool(expected), actual) + if err != nil { + t.Errorf("testBooleanObject failed: %s", err) + } + } +} + +func testIntegerObject(expected int64, actual object.Object) error { + result, ok := actual.(*object.Integer) + if !ok { + return fmt.Errorf("object is not Integer. got=%T (%+v)", + actual, actual) + } + + if result.Value != expected { + return fmt.Errorf("object has wrong value. got=%d, want=%d", + result.Value, expected) + } + + return nil +} + +func testBooleanObject(expected bool, actual object.Object) error { + result, ok := actual.(*object.Boolean) + if !ok { + return fmt.Errorf("object is not Boolean. got=%T (%+v)", + actual, actual) + } + + if result.Value != expected { + return fmt.Errorf("object has wrong value. got=%t, want=%t", + result.Value, expected) + } + + return nil +} diff --git a/wcig_code_1_2/04/.envrc b/wcig_code_1_2/04/.envrc new file mode 100644 index 0000000..fb65ef4 --- /dev/null +++ b/wcig_code_1_2/04/.envrc @@ -0,0 +1 @@ +export GOPATH=$(pwd) diff --git a/wcig_code_1_2/04/src/monkey/ast/ast.go b/wcig_code_1_2/04/src/monkey/ast/ast.go new file mode 100644 index 0000000..fb30b05 --- /dev/null +++ b/wcig_code_1_2/04/src/monkey/ast/ast.go @@ -0,0 +1,339 @@ +package ast + +import ( + "bytes" + "monkey/token" + "strings" +) + +// The base Node interface +type Node interface { + TokenLiteral() string + String() string +} + +// All statement nodes implement this +type Statement interface { + Node + statementNode() +} + +// All expression nodes implement this +type Expression interface { + Node + expressionNode() +} + +type Program struct { + Statements []Statement +} + +func (p *Program) TokenLiteral() string { + if len(p.Statements) > 0 { + return p.Statements[0].TokenLiteral() + } else { + return "" + } +} + +func (p *Program) String() string { + var out bytes.Buffer + + for _, s := range p.Statements { + out.WriteString(s.String()) + } + + return out.String() +} + +// Statements +type LetStatement struct { + Token token.Token // the token.LET token + Name *Identifier + Value Expression +} + +func (ls *LetStatement) statementNode() {} +func (ls *LetStatement) TokenLiteral() string { return ls.Token.Literal } +func (ls *LetStatement) String() string { + var out bytes.Buffer + + out.WriteString(ls.TokenLiteral() + " ") + out.WriteString(ls.Name.String()) + out.WriteString(" = ") + + if ls.Value != nil { + out.WriteString(ls.Value.String()) + } + + out.WriteString(";") + + return out.String() +} + +type ReturnStatement struct { + Token token.Token // the 'return' token + ReturnValue Expression +} + +func (rs *ReturnStatement) statementNode() {} +func (rs *ReturnStatement) TokenLiteral() string { return rs.Token.Literal } +func (rs *ReturnStatement) String() string { + var out bytes.Buffer + + out.WriteString(rs.TokenLiteral() + " ") + + if rs.ReturnValue != nil { + out.WriteString(rs.ReturnValue.String()) + } + + out.WriteString(";") + + return out.String() +} + +type ExpressionStatement struct { + Token token.Token // the first token of the expression + Expression Expression +} + +func (es *ExpressionStatement) statementNode() {} +func (es *ExpressionStatement) TokenLiteral() string { return es.Token.Literal } +func (es *ExpressionStatement) String() string { + if es.Expression != nil { + return es.Expression.String() + } + return "" +} + +type BlockStatement struct { + Token token.Token // the { token + Statements []Statement +} + +func (bs *BlockStatement) statementNode() {} +func (bs *BlockStatement) TokenLiteral() string { return bs.Token.Literal } +func (bs *BlockStatement) String() string { + var out bytes.Buffer + + for _, s := range bs.Statements { + out.WriteString(s.String()) + } + + return out.String() +} + +// Expressions +type Identifier struct { + Token token.Token // the token.IDENT token + Value string +} + +func (i *Identifier) expressionNode() {} +func (i *Identifier) TokenLiteral() string { return i.Token.Literal } +func (i *Identifier) String() string { return i.Value } + +type Boolean struct { + Token token.Token + Value bool +} + +func (b *Boolean) expressionNode() {} +func (b *Boolean) TokenLiteral() string { return b.Token.Literal } +func (b *Boolean) String() string { return b.Token.Literal } + +type IntegerLiteral struct { + Token token.Token + Value int64 +} + +func (il *IntegerLiteral) expressionNode() {} +func (il *IntegerLiteral) TokenLiteral() string { return il.Token.Literal } +func (il *IntegerLiteral) String() string { return il.Token.Literal } + +type PrefixExpression struct { + Token token.Token // The prefix token, e.g. ! + Operator string + Right Expression +} + +func (pe *PrefixExpression) expressionNode() {} +func (pe *PrefixExpression) TokenLiteral() string { return pe.Token.Literal } +func (pe *PrefixExpression) String() string { + var out bytes.Buffer + + out.WriteString("(") + out.WriteString(pe.Operator) + out.WriteString(pe.Right.String()) + out.WriteString(")") + + return out.String() +} + +type InfixExpression struct { + Token token.Token // The operator token, e.g. + + Left Expression + Operator string + Right Expression +} + +func (oe *InfixExpression) expressionNode() {} +func (oe *InfixExpression) TokenLiteral() string { return oe.Token.Literal } +func (oe *InfixExpression) String() string { + var out bytes.Buffer + + out.WriteString("(") + out.WriteString(oe.Left.String()) + out.WriteString(" " + oe.Operator + " ") + out.WriteString(oe.Right.String()) + out.WriteString(")") + + return out.String() +} + +type IfExpression struct { + Token token.Token // The 'if' token + Condition Expression + Consequence *BlockStatement + Alternative *BlockStatement +} + +func (ie *IfExpression) expressionNode() {} +func (ie *IfExpression) TokenLiteral() string { return ie.Token.Literal } +func (ie *IfExpression) String() string { + var out bytes.Buffer + + out.WriteString("if") + out.WriteString(ie.Condition.String()) + out.WriteString(" ") + out.WriteString(ie.Consequence.String()) + + if ie.Alternative != nil { + out.WriteString("else ") + out.WriteString(ie.Alternative.String()) + } + + return out.String() +} + +type FunctionLiteral struct { + Token token.Token // The 'fn' token + Parameters []*Identifier + Body *BlockStatement +} + +func (fl *FunctionLiteral) expressionNode() {} +func (fl *FunctionLiteral) TokenLiteral() string { return fl.Token.Literal } +func (fl *FunctionLiteral) String() string { + var out bytes.Buffer + + params := []string{} + for _, p := range fl.Parameters { + params = append(params, p.String()) + } + + out.WriteString(fl.TokenLiteral()) + out.WriteString("(") + out.WriteString(strings.Join(params, ", ")) + out.WriteString(") ") + out.WriteString(fl.Body.String()) + + return out.String() +} + +type CallExpression struct { + Token token.Token // The '(' token + Function Expression // Identifier or FunctionLiteral + Arguments []Expression +} + +func (ce *CallExpression) expressionNode() {} +func (ce *CallExpression) TokenLiteral() string { return ce.Token.Literal } +func (ce *CallExpression) String() string { + var out bytes.Buffer + + args := []string{} + for _, a := range ce.Arguments { + args = append(args, a.String()) + } + + out.WriteString(ce.Function.String()) + out.WriteString("(") + out.WriteString(strings.Join(args, ", ")) + out.WriteString(")") + + return out.String() +} + +type StringLiteral struct { + Token token.Token + Value string +} + +func (sl *StringLiteral) expressionNode() {} +func (sl *StringLiteral) TokenLiteral() string { return sl.Token.Literal } +func (sl *StringLiteral) String() string { return sl.Token.Literal } + +type ArrayLiteral struct { + Token token.Token // the '[' token + Elements []Expression +} + +func (al *ArrayLiteral) expressionNode() {} +func (al *ArrayLiteral) TokenLiteral() string { return al.Token.Literal } +func (al *ArrayLiteral) String() string { + var out bytes.Buffer + + elements := []string{} + for _, el := range al.Elements { + elements = append(elements, el.String()) + } + + out.WriteString("[") + out.WriteString(strings.Join(elements, ", ")) + out.WriteString("]") + + return out.String() +} + +type IndexExpression struct { + Token token.Token // The [ token + Left Expression + Index Expression +} + +func (ie *IndexExpression) expressionNode() {} +func (ie *IndexExpression) TokenLiteral() string { return ie.Token.Literal } +func (ie *IndexExpression) String() string { + var out bytes.Buffer + + out.WriteString("(") + out.WriteString(ie.Left.String()) + out.WriteString("[") + out.WriteString(ie.Index.String()) + out.WriteString("])") + + return out.String() +} + +type HashLiteral struct { + Token token.Token // the '{' token + Pairs map[Expression]Expression +} + +func (hl *HashLiteral) expressionNode() {} +func (hl *HashLiteral) TokenLiteral() string { return hl.Token.Literal } +func (hl *HashLiteral) String() string { + var out bytes.Buffer + + pairs := []string{} + for key, value := range hl.Pairs { + pairs = append(pairs, key.String()+":"+value.String()) + } + + out.WriteString("{") + out.WriteString(strings.Join(pairs, ", ")) + out.WriteString("}") + + return out.String() +} diff --git a/wcig_code_1_2/04/src/monkey/ast/ast_test.go b/wcig_code_1_2/04/src/monkey/ast/ast_test.go new file mode 100644 index 0000000..14a49dc --- /dev/null +++ b/wcig_code_1_2/04/src/monkey/ast/ast_test.go @@ -0,0 +1,28 @@ +package ast + +import ( + "monkey/token" + "testing" +) + +func TestString(t *testing.T) { + program := &Program{ + Statements: []Statement{ + &LetStatement{ + Token: token.Token{Type: token.LET, Literal: "let"}, + Name: &Identifier{ + Token: token.Token{Type: token.IDENT, Literal: "myVar"}, + Value: "myVar", + }, + Value: &Identifier{ + Token: token.Token{Type: token.IDENT, Literal: "anotherVar"}, + Value: "anotherVar", + }, + }, + }, + } + + if program.String() != "let myVar = anotherVar;" { + t.Errorf("program.String() wrong. got=%q", program.String()) + } +} diff --git a/wcig_code_1_2/04/src/monkey/code/code.go b/wcig_code_1_2/04/src/monkey/code/code.go new file mode 100644 index 0000000..23b79a5 --- /dev/null +++ b/wcig_code_1_2/04/src/monkey/code/code.go @@ -0,0 +1,165 @@ +package code + +import ( + "bytes" + "encoding/binary" + "fmt" +) + +type Instructions []byte + +func (ins Instructions) String() string { + var out bytes.Buffer + + i := 0 + for i < len(ins) { + def, err := Lookup(ins[i]) + if err != nil { + fmt.Fprintf(&out, "ERROR: %s\n", err) + continue + } + + operands, read := ReadOperands(def, ins[i+1:]) + + fmt.Fprintf(&out, "%04d %s\n", i, ins.fmtInstruction(def, operands)) + + i += 1 + read + } + + return out.String() +} + +func (ins Instructions) fmtInstruction(def *Definition, operands []int) string { + operandCount := len(def.OperandWidths) + + if len(operands) != operandCount { + return fmt.Sprintf("ERROR: operand len %d does not match defined %d\n", + len(operands), operandCount) + } + + switch operandCount { + case 0: + return def.Name + case 1: + return fmt.Sprintf("%s %d", def.Name, operands[0]) + } + + return fmt.Sprintf("ERROR: unhandled operandCount for %s\n", def.Name) +} + +type Opcode byte + +const ( + OpConstant Opcode = iota + + OpAdd + + OpPop + + OpSub + OpMul + OpDiv + + OpTrue + OpFalse + + OpEqual + OpNotEqual + OpGreaterThan + + OpMinus + OpBang + + OpJumpNotTruthy + OpJump + + OpNull +) + +type Definition struct { + Name string + OperandWidths []int +} + +var definitions = map[Opcode]*Definition{ + OpConstant: {"OpConstant", []int{2}}, + + OpAdd: {"OpAdd", []int{}}, + + OpPop: {"OpPop", []int{}}, + + OpSub: {"OpSub", []int{}}, + OpMul: {"OpMul", []int{}}, + OpDiv: {"OpDiv", []int{}}, + + OpTrue: {"OpTrue", []int{}}, + OpFalse: {"OpFalse", []int{}}, + + OpEqual: {"OpEqual", []int{}}, + OpNotEqual: {"OpNotEqual", []int{}}, + OpGreaterThan: {"OpGreaterThan", []int{}}, + + OpMinus: {"OpMinus", []int{}}, + OpBang: {"OpBang", []int{}}, + + OpJumpNotTruthy: {"OpJumpNotTruthy", []int{2}}, + OpJump: {"OpJump", []int{2}}, + + OpNull: {"OpNull", []int{}}, +} + +func Lookup(op byte) (*Definition, error) { + def, ok := definitions[Opcode(op)] + if !ok { + return nil, fmt.Errorf("opcode %d undefined", op) + } + + return def, nil +} + +func Make(op Opcode, operands ...int) []byte { + def, ok := definitions[op] + if !ok { + return []byte{} + } + + instructionLen := 1 + for _, w := range def.OperandWidths { + instructionLen += w + } + + instruction := make([]byte, instructionLen) + instruction[0] = byte(op) + + offset := 1 + for i, o := range operands { + width := def.OperandWidths[i] + switch width { + case 2: + binary.BigEndian.PutUint16(instruction[offset:], uint16(o)) + } + offset += width + } + + return instruction +} + +func ReadOperands(def *Definition, ins Instructions) ([]int, int) { + operands := make([]int, len(def.OperandWidths)) + offset := 0 + + for i, width := range def.OperandWidths { + switch width { + case 2: + operands[i] = int(ReadUint16(ins[offset:])) + } + + offset += width + } + + return operands, offset +} + +func ReadUint16(ins Instructions) uint16 { + return binary.BigEndian.Uint16(ins) +} diff --git a/wcig_code_1_2/04/src/monkey/code/code_test.go b/wcig_code_1_2/04/src/monkey/code/code_test.go new file mode 100644 index 0000000..a353025 --- /dev/null +++ b/wcig_code_1_2/04/src/monkey/code/code_test.go @@ -0,0 +1,83 @@ +package code + +import "testing" + +func TestMake(t *testing.T) { + tests := []struct { + op Opcode + operands []int + expected []byte + }{ + {OpConstant, []int{65534}, []byte{byte(OpConstant), 255, 254}}, + {OpAdd, []int{}, []byte{byte(OpAdd)}}, + } + + for _, tt := range tests { + instruction := Make(tt.op, tt.operands...) + + if len(instruction) != len(tt.expected) { + t.Errorf("instruction has wrong length. want=%d, got=%d", + len(tt.expected), len(instruction)) + } + + for i, b := range tt.expected { + if instruction[i] != tt.expected[i] { + t.Errorf("wrong byte at pos %d. want=%d, got=%d", + i, b, instruction[i]) + } + } + } +} + +func TestInstructionsString(t *testing.T) { + instructions := []Instructions{ + Make(OpAdd), + Make(OpConstant, 2), + Make(OpConstant, 65535), + } + + expected := `0000 OpAdd +0001 OpConstant 2 +0004 OpConstant 65535 +` + + concatted := Instructions{} + for _, ins := range instructions { + concatted = append(concatted, ins...) + } + + if concatted.String() != expected { + t.Errorf("instructions wrongly formatted.\nwant=%q\ngot=%q", + expected, concatted.String()) + } +} + +func TestReadOperands(t *testing.T) { + tests := []struct { + op Opcode + operands []int + bytesRead int + }{ + {OpConstant, []int{65535}, 2}, + } + + for _, tt := range tests { + instruction := Make(tt.op, tt.operands...) + + def, err := Lookup(byte(tt.op)) + if err != nil { + t.Fatalf("definition not found: %q\n", err) + } + + operandsRead, n := ReadOperands(def, instruction[1:]) + if n != tt.bytesRead { + t.Fatalf("n wrong. want=%d, got=%d", tt.bytesRead, n) + } + + for i, want := range tt.operands { + if operandsRead[i] != want { + t.Errorf("operand wrong. want=%d, got=%d", want, operandsRead[i]) + } + } + } +} diff --git a/wcig_code_1_2/04/src/monkey/compiler/compiler.go b/wcig_code_1_2/04/src/monkey/compiler/compiler.go new file mode 100644 index 0000000..011091e --- /dev/null +++ b/wcig_code_1_2/04/src/monkey/compiler/compiler.go @@ -0,0 +1,231 @@ +package compiler + +import ( + "fmt" + "monkey/ast" + "monkey/code" + "monkey/object" +) + +type Compiler struct { + instructions code.Instructions + constants []object.Object + + lastInstruction EmittedInstruction + previousInstruction EmittedInstruction +} + +func New() *Compiler { + return &Compiler{ + instructions: code.Instructions{}, + constants: []object.Object{}, + lastInstruction: EmittedInstruction{}, + previousInstruction: EmittedInstruction{}, + } +} + +func (c *Compiler) Compile(node ast.Node) error { + switch node := node.(type) { + case *ast.Program: + for _, s := range node.Statements { + err := c.Compile(s) + if err != nil { + return err + } + } + + case *ast.ExpressionStatement: + err := c.Compile(node.Expression) + if err != nil { + return err + } + c.emit(code.OpPop) + + case *ast.InfixExpression: + if node.Operator == "<" { + err := c.Compile(node.Right) + if err != nil { + return err + } + + err = c.Compile(node.Left) + if err != nil { + return err + } + c.emit(code.OpGreaterThan) + return nil + } + + err := c.Compile(node.Left) + if err != nil { + return err + } + + err = c.Compile(node.Right) + if err != nil { + return err + } + + switch node.Operator { + case "+": + c.emit(code.OpAdd) + case "-": + c.emit(code.OpSub) + case "*": + c.emit(code.OpMul) + case "/": + c.emit(code.OpDiv) + case ">": + c.emit(code.OpGreaterThan) + case "==": + c.emit(code.OpEqual) + case "!=": + c.emit(code.OpNotEqual) + default: + return fmt.Errorf("unknown operator %s", node.Operator) + } + + case *ast.IntegerLiteral: + integer := &object.Integer{Value: node.Value} + c.emit(code.OpConstant, c.addConstant(integer)) + + case *ast.Boolean: + if node.Value { + c.emit(code.OpTrue) + } else { + c.emit(code.OpFalse) + } + + case *ast.PrefixExpression: + err := c.Compile(node.Right) + if err != nil { + return err + } + + switch node.Operator { + case "!": + c.emit(code.OpBang) + case "-": + c.emit(code.OpMinus) + default: + return fmt.Errorf("unknown operator %s", node.Operator) + } + + case *ast.IfExpression: + err := c.Compile(node.Condition) + if err != nil { + return err + } + + // Emit an `OpJumpNotTruthy` with a bogus value + jumpNotTruthyPos := c.emit(code.OpJumpNotTruthy, 9999) + + err = c.Compile(node.Consequence) + if err != nil { + return err + } + + if c.lastInstructionIsPop() { + c.removeLastPop() + } + + // Emit an `OpJump` with a bogus value + jumpPos := c.emit(code.OpJump, 9999) + + afterConsequencePos := len(c.instructions) + c.changeOperand(jumpNotTruthyPos, afterConsequencePos) + + if node.Alternative == nil { + c.emit(code.OpNull) + } else { + err := c.Compile(node.Alternative) + if err != nil { + return err + } + + if c.lastInstructionIsPop() { + c.removeLastPop() + } + } + + afterAlternativePos := len(c.instructions) + c.changeOperand(jumpPos, afterAlternativePos) + + case *ast.BlockStatement: + for _, s := range node.Statements { + err := c.Compile(s) + if err != nil { + return err + } + } + } + + return nil +} + +func (c *Compiler) Bytecode() *Bytecode { + return &Bytecode{ + Instructions: c.instructions, + Constants: c.constants, + } +} + +func (c *Compiler) addConstant(obj object.Object) int { + c.constants = append(c.constants, obj) + return len(c.constants) - 1 +} + +func (c *Compiler) emit(op code.Opcode, operands ...int) int { + ins := code.Make(op, operands...) + pos := c.addInstruction(ins) + + c.setLastInstruction(op, pos) + + return pos +} + +func (c *Compiler) addInstruction(ins []byte) int { + posNewInstruction := len(c.instructions) + c.instructions = append(c.instructions, ins...) + return posNewInstruction +} + +func (c *Compiler) setLastInstruction(op code.Opcode, pos int) { + previous := c.lastInstruction + last := EmittedInstruction{Opcode: op, Position: pos} + + c.previousInstruction = previous + c.lastInstruction = last +} + +func (c *Compiler) lastInstructionIsPop() bool { + return c.lastInstruction.Opcode == code.OpPop +} + +func (c *Compiler) removeLastPop() { + c.instructions = c.instructions[:c.lastInstruction.Position] + c.lastInstruction = c.previousInstruction +} + +func (c *Compiler) replaceInstruction(pos int, newInstruction []byte) { + for i := 0; i < len(newInstruction); i++ { + c.instructions[pos+i] = newInstruction[i] + } +} + +func (c *Compiler) changeOperand(opPos int, operand int) { + op := code.Opcode(c.instructions[opPos]) + newInstruction := code.Make(op, operand) + + c.replaceInstruction(opPos, newInstruction) +} + +type Bytecode struct { + Instructions code.Instructions + Constants []object.Object +} + +type EmittedInstruction struct { + Opcode code.Opcode + Position int +} diff --git a/wcig_code_1_2/04/src/monkey/compiler/compiler_test.go b/wcig_code_1_2/04/src/monkey/compiler/compiler_test.go new file mode 100644 index 0000000..68ba95e --- /dev/null +++ b/wcig_code_1_2/04/src/monkey/compiler/compiler_test.go @@ -0,0 +1,332 @@ +package compiler + +import ( + "fmt" + "monkey/ast" + "monkey/code" + "monkey/lexer" + "monkey/object" + "monkey/parser" + "testing" +) + +func TestIntegerArithmetic(t *testing.T) { + tests := []compilerTestCase{ + { + input: "1 + 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpAdd), + code.Make(code.OpPop), + }, + }, + { + input: "1; 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpPop), + code.Make(code.OpConstant, 1), + code.Make(code.OpPop), + }, + }, + { + input: "1 - 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpSub), + code.Make(code.OpPop), + }, + }, + { + input: "1 * 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpMul), + code.Make(code.OpPop), + }, + }, + { + input: "2 / 1", + expectedConstants: []interface{}{2, 1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpDiv), + code.Make(code.OpPop), + }, + }, + { + input: "-1", + expectedConstants: []interface{}{1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpMinus), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestBooleanExpressions(t *testing.T) { + tests := []compilerTestCase{ + { + input: "true", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpTrue), + code.Make(code.OpPop), + }, + }, + { + input: "false", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpFalse), + code.Make(code.OpPop), + }, + }, + { + input: "1 > 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpGreaterThan), + code.Make(code.OpPop), + }, + }, + { + input: "1 < 2", + expectedConstants: []interface{}{2, 1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpGreaterThan), + code.Make(code.OpPop), + }, + }, + { + input: "1 == 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpEqual), + code.Make(code.OpPop), + }, + }, + { + input: "1 != 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpNotEqual), + code.Make(code.OpPop), + }, + }, + { + input: "true == false", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpTrue), + code.Make(code.OpFalse), + code.Make(code.OpEqual), + code.Make(code.OpPop), + }, + }, + { + input: "true != false", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpTrue), + code.Make(code.OpFalse), + code.Make(code.OpNotEqual), + code.Make(code.OpPop), + }, + }, + { + input: "!true", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpTrue), + code.Make(code.OpBang), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestConditionals(t *testing.T) { + tests := []compilerTestCase{ + { + input: ` + if (true) { 10 }; 3333; + `, + expectedConstants: []interface{}{10, 3333}, + expectedInstructions: []code.Instructions{ + // 0000 + code.Make(code.OpTrue), + // 0001 + code.Make(code.OpJumpNotTruthy, 10), + // 0004 + code.Make(code.OpConstant, 0), + // 0007 + code.Make(code.OpJump, 11), + // 0010 + code.Make(code.OpNull), + // 0011 + code.Make(code.OpPop), + // 0012 + code.Make(code.OpConstant, 1), + // 0015 + code.Make(code.OpPop), + }, + }, + { + input: ` + if (true) { 10 } else { 20 }; 3333; + `, + expectedConstants: []interface{}{10, 20, 3333}, + expectedInstructions: []code.Instructions{ + // 0000 + code.Make(code.OpTrue), + // 0001 + code.Make(code.OpJumpNotTruthy, 10), + // 0004 + code.Make(code.OpConstant, 0), + // 0007 + code.Make(code.OpJump, 13), + // 0010 + code.Make(code.OpConstant, 1), + // 0013 + code.Make(code.OpPop), + // 0014 + code.Make(code.OpConstant, 2), + // 0017 + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +type compilerTestCase struct { + input string + expectedConstants []interface{} + expectedInstructions []code.Instructions +} + +func runCompilerTests(t *testing.T, tests []compilerTestCase) { + t.Helper() + + for _, tt := range tests { + program := parse(tt.input) + + compiler := New() + err := compiler.Compile(program) + if err != nil { + t.Fatalf("compiler error: %s", err) + } + + bytecode := compiler.Bytecode() + + err = testInstructions(tt.expectedInstructions, bytecode.Instructions) + if err != nil { + t.Fatalf("testInstructions failed: %s", err) + } + + err = testConstants(t, tt.expectedConstants, bytecode.Constants) + if err != nil { + t.Fatalf("testConstants failed: %s", err) + } + } +} + +func parse(input string) *ast.Program { + l := lexer.New(input) + p := parser.New(l) + return p.ParseProgram() +} + +func testInstructions( + expected []code.Instructions, + actual code.Instructions, +) error { + concatted := concatInstructions(expected) + + if len(actual) != len(concatted) { + return fmt.Errorf("wrong instructions length.\nwant=%q\ngot =%q", + concatted, actual) + } + + for i, ins := range concatted { + if actual[i] != ins { + return fmt.Errorf("wrong instruction at %d.\nwant=%q\ngot =%q", + i, concatted, actual) + } + } + + return nil +} + +func concatInstructions(s []code.Instructions) code.Instructions { + out := code.Instructions{} + + for _, ins := range s { + out = append(out, ins...) + } + + return out +} + +func testConstants( + t *testing.T, + expected []interface{}, + actual []object.Object, +) error { + if len(expected) != len(actual) { + return fmt.Errorf("wrong number of constants. got=%d, want=%d", + len(actual), len(expected)) + } + + for i, constant := range expected { + switch constant := constant.(type) { + case int: + err := testIntegerObject(int64(constant), actual[i]) + if err != nil { + return fmt.Errorf("constant %d - testIntegerObject failed: %s", + i, err) + } + } + } + + return nil +} + +func testIntegerObject(expected int64, actual object.Object) error { + result, ok := actual.(*object.Integer) + if !ok { + return fmt.Errorf("object is not Integer. got=%T (%+v)", + actual, actual) + } + + if result.Value != expected { + return fmt.Errorf("object has wrong value. got=%d, want=%d", + result.Value, expected) + } + + return nil +} diff --git a/wcig_code_1_2/04/src/monkey/evaluator/builtins.go b/wcig_code_1_2/04/src/monkey/evaluator/builtins.go new file mode 100644 index 0000000..c4fb0f3 --- /dev/null +++ b/wcig_code_1_2/04/src/monkey/evaluator/builtins.go @@ -0,0 +1,117 @@ +package evaluator + +import ( + "fmt" + "monkey/object" +) + +var builtins = map[string]*object.Builtin{ + "len": &object.Builtin{Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", + len(args)) + } + + switch arg := args[0].(type) { + case *object.Array: + return &object.Integer{Value: int64(len(arg.Elements))} + case *object.String: + return &object.Integer{Value: int64(len(arg.Value))} + default: + return newError("argument to `len` not supported, got %s", + args[0].Type()) + } + }, + }, + "puts": &object.Builtin{ + Fn: func(args ...object.Object) object.Object { + for _, arg := range args { + fmt.Println(arg.Inspect()) + } + + return NULL + }, + }, + "first": &object.Builtin{ + Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", + len(args)) + } + if args[0].Type() != object.ARRAY_OBJ { + return newError("argument to `first` must be ARRAY, got %s", + args[0].Type()) + } + + arr := args[0].(*object.Array) + if len(arr.Elements) > 0 { + return arr.Elements[0] + } + + return NULL + }, + }, + "last": &object.Builtin{ + Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", + len(args)) + } + if args[0].Type() != object.ARRAY_OBJ { + return newError("argument to `last` must be ARRAY, got %s", + args[0].Type()) + } + + arr := args[0].(*object.Array) + length := len(arr.Elements) + if length > 0 { + return arr.Elements[length-1] + } + + return NULL + }, + }, + "rest": &object.Builtin{ + Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", + len(args)) + } + if args[0].Type() != object.ARRAY_OBJ { + return newError("argument to `rest` must be ARRAY, got %s", + args[0].Type()) + } + + arr := args[0].(*object.Array) + length := len(arr.Elements) + if length > 0 { + newElements := make([]object.Object, length-1, length-1) + copy(newElements, arr.Elements[1:length]) + return &object.Array{Elements: newElements} + } + + return NULL + }, + }, + "push": &object.Builtin{ + Fn: func(args ...object.Object) object.Object { + if len(args) != 2 { + return newError("wrong number of arguments. got=%d, want=2", + len(args)) + } + if args[0].Type() != object.ARRAY_OBJ { + return newError("argument to `push` must be ARRAY, got %s", + args[0].Type()) + } + + arr := args[0].(*object.Array) + length := len(arr.Elements) + + newElements := make([]object.Object, length+1, length+1) + copy(newElements, arr.Elements) + newElements[length] = args[1] + + return &object.Array{Elements: newElements} + }, + }, +} diff --git a/wcig_code_1_2/04/src/monkey/evaluator/evaluator.go b/wcig_code_1_2/04/src/monkey/evaluator/evaluator.go new file mode 100644 index 0000000..50b2ab5 --- /dev/null +++ b/wcig_code_1_2/04/src/monkey/evaluator/evaluator.go @@ -0,0 +1,442 @@ +package evaluator + +import ( + "fmt" + "monkey/ast" + "monkey/object" +) + +var ( + NULL = &object.Null{} + TRUE = &object.Boolean{Value: true} + FALSE = &object.Boolean{Value: false} +) + +func Eval(node ast.Node, env *object.Environment) object.Object { + switch node := node.(type) { + + // Statements + case *ast.Program: + return evalProgram(node, env) + + case *ast.BlockStatement: + return evalBlockStatement(node, env) + + case *ast.ExpressionStatement: + return Eval(node.Expression, env) + + case *ast.ReturnStatement: + val := Eval(node.ReturnValue, env) + if isError(val) { + return val + } + return &object.ReturnValue{Value: val} + + case *ast.LetStatement: + val := Eval(node.Value, env) + if isError(val) { + return val + } + env.Set(node.Name.Value, val) + + // Expressions + case *ast.IntegerLiteral: + return &object.Integer{Value: node.Value} + + case *ast.StringLiteral: + return &object.String{Value: node.Value} + + case *ast.Boolean: + return nativeBoolToBooleanObject(node.Value) + + case *ast.PrefixExpression: + right := Eval(node.Right, env) + if isError(right) { + return right + } + return evalPrefixExpression(node.Operator, right) + + case *ast.InfixExpression: + left := Eval(node.Left, env) + if isError(left) { + return left + } + + right := Eval(node.Right, env) + if isError(right) { + return right + } + + return evalInfixExpression(node.Operator, left, right) + + case *ast.IfExpression: + return evalIfExpression(node, env) + + case *ast.Identifier: + return evalIdentifier(node, env) + + case *ast.FunctionLiteral: + params := node.Parameters + body := node.Body + return &object.Function{Parameters: params, Env: env, Body: body} + + case *ast.CallExpression: + function := Eval(node.Function, env) + if isError(function) { + return function + } + + args := evalExpressions(node.Arguments, env) + if len(args) == 1 && isError(args[0]) { + return args[0] + } + + return applyFunction(function, args) + + case *ast.ArrayLiteral: + elements := evalExpressions(node.Elements, env) + if len(elements) == 1 && isError(elements[0]) { + return elements[0] + } + return &object.Array{Elements: elements} + + case *ast.IndexExpression: + left := Eval(node.Left, env) + if isError(left) { + return left + } + index := Eval(node.Index, env) + if isError(index) { + return index + } + return evalIndexExpression(left, index) + + case *ast.HashLiteral: + return evalHashLiteral(node, env) + + } + + return nil +} + +func evalProgram(program *ast.Program, env *object.Environment) object.Object { + var result object.Object + + for _, statement := range program.Statements { + result = Eval(statement, env) + + switch result := result.(type) { + case *object.ReturnValue: + return result.Value + case *object.Error: + return result + } + } + + return result +} + +func evalBlockStatement( + block *ast.BlockStatement, + env *object.Environment, +) object.Object { + var result object.Object + + for _, statement := range block.Statements { + result = Eval(statement, env) + + if result != nil { + rt := result.Type() + if rt == object.RETURN_VALUE_OBJ || rt == object.ERROR_OBJ { + return result + } + } + } + + return result +} + +func nativeBoolToBooleanObject(input bool) *object.Boolean { + if input { + return TRUE + } + return FALSE +} + +func evalPrefixExpression(operator string, right object.Object) object.Object { + switch operator { + case "!": + return evalBangOperatorExpression(right) + case "-": + return evalMinusPrefixOperatorExpression(right) + default: + return newError("unknown operator: %s%s", operator, right.Type()) + } +} + +func evalInfixExpression( + operator string, + left, right object.Object, +) object.Object { + switch { + case left.Type() == object.INTEGER_OBJ && right.Type() == object.INTEGER_OBJ: + return evalIntegerInfixExpression(operator, left, right) + case left.Type() == object.STRING_OBJ && right.Type() == object.STRING_OBJ: + return evalStringInfixExpression(operator, left, right) + case operator == "==": + return nativeBoolToBooleanObject(left == right) + case operator == "!=": + return nativeBoolToBooleanObject(left != right) + case left.Type() != right.Type(): + return newError("type mismatch: %s %s %s", + left.Type(), operator, right.Type()) + default: + return newError("unknown operator: %s %s %s", + left.Type(), operator, right.Type()) + } +} + +func evalBangOperatorExpression(right object.Object) object.Object { + switch right { + case TRUE: + return FALSE + case FALSE: + return TRUE + case NULL: + return TRUE + default: + return FALSE + } +} + +func evalMinusPrefixOperatorExpression(right object.Object) object.Object { + if right.Type() != object.INTEGER_OBJ { + return newError("unknown operator: -%s", right.Type()) + } + + value := right.(*object.Integer).Value + return &object.Integer{Value: -value} +} + +func evalIntegerInfixExpression( + operator string, + left, right object.Object, +) object.Object { + leftVal := left.(*object.Integer).Value + rightVal := right.(*object.Integer).Value + + switch operator { + case "+": + return &object.Integer{Value: leftVal + rightVal} + case "-": + return &object.Integer{Value: leftVal - rightVal} + case "*": + return &object.Integer{Value: leftVal * rightVal} + case "/": + return &object.Integer{Value: leftVal / rightVal} + case "<": + return nativeBoolToBooleanObject(leftVal < rightVal) + case ">": + return nativeBoolToBooleanObject(leftVal > rightVal) + case "==": + return nativeBoolToBooleanObject(leftVal == rightVal) + case "!=": + return nativeBoolToBooleanObject(leftVal != rightVal) + default: + return newError("unknown operator: %s %s %s", + left.Type(), operator, right.Type()) + } +} + +func evalStringInfixExpression( + operator string, + left, right object.Object, +) object.Object { + if operator != "+" { + return newError("unknown operator: %s %s %s", + left.Type(), operator, right.Type()) + } + + leftVal := left.(*object.String).Value + rightVal := right.(*object.String).Value + return &object.String{Value: leftVal + rightVal} +} + +func evalIfExpression( + ie *ast.IfExpression, + env *object.Environment, +) object.Object { + condition := Eval(ie.Condition, env) + if isError(condition) { + return condition + } + + if isTruthy(condition) { + return Eval(ie.Consequence, env) + } else if ie.Alternative != nil { + return Eval(ie.Alternative, env) + } else { + return NULL + } +} + +func evalIdentifier( + node *ast.Identifier, + env *object.Environment, +) object.Object { + if val, ok := env.Get(node.Value); ok { + return val + } + + if builtin, ok := builtins[node.Value]; ok { + return builtin + } + + return newError("identifier not found: " + node.Value) +} + +func isTruthy(obj object.Object) bool { + switch obj { + case NULL: + return false + case TRUE: + return true + case FALSE: + return false + default: + return true + } +} + +func newError(format string, a ...interface{}) *object.Error { + return &object.Error{Message: fmt.Sprintf(format, a...)} +} + +func isError(obj object.Object) bool { + if obj != nil { + return obj.Type() == object.ERROR_OBJ + } + return false +} + +func evalExpressions( + exps []ast.Expression, + env *object.Environment, +) []object.Object { + var result []object.Object + + for _, e := range exps { + evaluated := Eval(e, env) + if isError(evaluated) { + return []object.Object{evaluated} + } + result = append(result, evaluated) + } + + return result +} + +func applyFunction(fn object.Object, args []object.Object) object.Object { + switch fn := fn.(type) { + + case *object.Function: + extendedEnv := extendFunctionEnv(fn, args) + evaluated := Eval(fn.Body, extendedEnv) + return unwrapReturnValue(evaluated) + + case *object.Builtin: + return fn.Fn(args...) + + default: + return newError("not a function: %s", fn.Type()) + } +} + +func extendFunctionEnv( + fn *object.Function, + args []object.Object, +) *object.Environment { + env := object.NewEnclosedEnvironment(fn.Env) + + for paramIdx, param := range fn.Parameters { + env.Set(param.Value, args[paramIdx]) + } + + return env +} + +func unwrapReturnValue(obj object.Object) object.Object { + if returnValue, ok := obj.(*object.ReturnValue); ok { + return returnValue.Value + } + + return obj +} + +func evalIndexExpression(left, index object.Object) object.Object { + switch { + case left.Type() == object.ARRAY_OBJ && index.Type() == object.INTEGER_OBJ: + return evalArrayIndexExpression(left, index) + case left.Type() == object.HASH_OBJ: + return evalHashIndexExpression(left, index) + default: + return newError("index operator not supported: %s", left.Type()) + } +} + +func evalArrayIndexExpression(array, index object.Object) object.Object { + arrayObject := array.(*object.Array) + idx := index.(*object.Integer).Value + max := int64(len(arrayObject.Elements) - 1) + + if idx < 0 || idx > max { + return NULL + } + + return arrayObject.Elements[idx] +} + +func evalHashLiteral( + node *ast.HashLiteral, + env *object.Environment, +) object.Object { + pairs := make(map[object.HashKey]object.HashPair) + + for keyNode, valueNode := range node.Pairs { + key := Eval(keyNode, env) + if isError(key) { + return key + } + + hashKey, ok := key.(object.Hashable) + if !ok { + return newError("unusable as hash key: %s", key.Type()) + } + + value := Eval(valueNode, env) + if isError(value) { + return value + } + + hashed := hashKey.HashKey() + pairs[hashed] = object.HashPair{Key: key, Value: value} + } + + return &object.Hash{Pairs: pairs} +} + +func evalHashIndexExpression(hash, index object.Object) object.Object { + hashObject := hash.(*object.Hash) + + key, ok := index.(object.Hashable) + if !ok { + return newError("unusable as hash key: %s", index.Type()) + } + + pair, ok := hashObject.Pairs[key.HashKey()] + if !ok { + return NULL + } + + return pair.Value +} diff --git a/wcig_code_1_2/04/src/monkey/evaluator/evaluator_test.go b/wcig_code_1_2/04/src/monkey/evaluator/evaluator_test.go new file mode 100644 index 0000000..2304e6f --- /dev/null +++ b/wcig_code_1_2/04/src/monkey/evaluator/evaluator_test.go @@ -0,0 +1,629 @@ +package evaluator + +import ( + "monkey/lexer" + "monkey/object" + "monkey/parser" + "testing" +) + +func TestEvalIntegerExpression(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"5", 5}, + {"10", 10}, + {"-5", -5}, + {"-10", -10}, + {"5 + 5 + 5 + 5 - 10", 10}, + {"2 * 2 * 2 * 2 * 2", 32}, + {"-50 + 100 + -50", 0}, + {"5 * 2 + 10", 20}, + {"5 + 2 * 10", 25}, + {"20 + 2 * -10", 0}, + {"50 / 2 * 2 + 10", 60}, + {"2 * (5 + 10)", 30}, + {"3 * 3 * 3 + 10", 37}, + {"3 * (3 * 3) + 10", 37}, + {"(5 + 10 * 2 + 15 / 3) * 2 + -10", 50}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testIntegerObject(t, evaluated, tt.expected) + } +} + +func TestEvalBooleanExpression(t *testing.T) { + tests := []struct { + input string + expected bool + }{ + {"true", true}, + {"false", false}, + {"1 < 2", true}, + {"1 > 2", false}, + {"1 < 1", false}, + {"1 > 1", false}, + {"1 == 1", true}, + {"1 != 1", false}, + {"1 == 2", false}, + {"1 != 2", true}, + {"true == true", true}, + {"false == false", true}, + {"true == false", false}, + {"true != false", true}, + {"false != true", true}, + {"(1 < 2) == true", true}, + {"(1 < 2) == false", false}, + {"(1 > 2) == true", false}, + {"(1 > 2) == false", true}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testBooleanObject(t, evaluated, tt.expected) + } +} + +func TestBangOperator(t *testing.T) { + tests := []struct { + input string + expected bool + }{ + {"!true", false}, + {"!false", true}, + {"!5", false}, + {"!!true", true}, + {"!!false", false}, + {"!!5", true}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testBooleanObject(t, evaluated, tt.expected) + } +} + +func TestIfElseExpressions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + {"if (true) { 10 }", 10}, + {"if (false) { 10 }", nil}, + {"if (1) { 10 }", 10}, + {"if (1 < 2) { 10 }", 10}, + {"if (1 > 2) { 10 }", nil}, + {"if (1 > 2) { 10 } else { 20 }", 20}, + {"if (1 < 2) { 10 } else { 20 }", 10}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + integer, ok := tt.expected.(int) + if ok { + testIntegerObject(t, evaluated, int64(integer)) + } else { + testNullObject(t, evaluated) + } + } +} + +func TestReturnStatements(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"return 10;", 10}, + {"return 10; 9;", 10}, + {"return 2 * 5; 9;", 10}, + {"9; return 2 * 5; 9;", 10}, + {"if (10 > 1) { return 10; }", 10}, + { + ` +if (10 > 1) { + if (10 > 1) { + return 10; + } + + return 1; +} +`, + 10, + }, + { + ` +let f = fn(x) { + return x; + x + 10; +}; +f(10);`, + 10, + }, + { + ` +let f = fn(x) { + let result = x + 10; + return result; + return 10; +}; +f(10);`, + 20, + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testIntegerObject(t, evaluated, tt.expected) + } +} + +func TestErrorHandling(t *testing.T) { + tests := []struct { + input string + expectedMessage string + }{ + { + "5 + true;", + "type mismatch: INTEGER + BOOLEAN", + }, + { + "5 + true; 5;", + "type mismatch: INTEGER + BOOLEAN", + }, + { + "-true", + "unknown operator: -BOOLEAN", + }, + { + "true + false;", + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + "true + false + true + false;", + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + "5; true + false; 5", + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + `"Hello" - "World"`, + "unknown operator: STRING - STRING", + }, + { + "if (10 > 1) { true + false; }", + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + ` +if (10 > 1) { + if (10 > 1) { + return true + false; + } + + return 1; +} +`, + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + "foobar", + "identifier not found: foobar", + }, + { + `{"name": "Monkey"}[fn(x) { x }];`, + "unusable as hash key: FUNCTION", + }, + { + `999[1]`, + "index operator not supported: INTEGER", + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + + errObj, ok := evaluated.(*object.Error) + if !ok { + t.Errorf("no error object returned. got=%T(%+v)", + evaluated, evaluated) + continue + } + + if errObj.Message != tt.expectedMessage { + t.Errorf("wrong error message. expected=%q, got=%q", + tt.expectedMessage, errObj.Message) + } + } +} + +func TestLetStatements(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"let a = 5; a;", 5}, + {"let a = 5 * 5; a;", 25}, + {"let a = 5; let b = a; b;", 5}, + {"let a = 5; let b = a; let c = a + b + 5; c;", 15}, + } + + for _, tt := range tests { + testIntegerObject(t, testEval(tt.input), tt.expected) + } +} + +func TestFunctionObject(t *testing.T) { + input := "fn(x) { x + 2; };" + + evaluated := testEval(input) + fn, ok := evaluated.(*object.Function) + if !ok { + t.Fatalf("object is not Function. got=%T (%+v)", evaluated, evaluated) + } + + if len(fn.Parameters) != 1 { + t.Fatalf("function has wrong parameters. Parameters=%+v", + fn.Parameters) + } + + if fn.Parameters[0].String() != "x" { + t.Fatalf("parameter is not 'x'. got=%q", fn.Parameters[0]) + } + + expectedBody := "(x + 2)" + + if fn.Body.String() != expectedBody { + t.Fatalf("body is not %q. got=%q", expectedBody, fn.Body.String()) + } +} + +func TestFunctionApplication(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"let identity = fn(x) { x; }; identity(5);", 5}, + {"let identity = fn(x) { return x; }; identity(5);", 5}, + {"let double = fn(x) { x * 2; }; double(5);", 10}, + {"let add = fn(x, y) { x + y; }; add(5, 5);", 10}, + {"let add = fn(x, y) { x + y; }; add(5 + 5, add(5, 5));", 20}, + {"fn(x) { x; }(5)", 5}, + } + + for _, tt := range tests { + testIntegerObject(t, testEval(tt.input), tt.expected) + } +} + +func TestEnclosingEnvironments(t *testing.T) { + input := ` +let first = 10; +let second = 10; +let third = 10; + +let ourFunction = fn(first) { + let second = 20; + + first + second + third; +}; + +ourFunction(20) + first + second;` + + testIntegerObject(t, testEval(input), 70) +} + +func TestClosures(t *testing.T) { + input := ` +let newAdder = fn(x) { + fn(y) { x + y }; +}; + +let addTwo = newAdder(2); +addTwo(2);` + + testIntegerObject(t, testEval(input), 4) +} + +func TestStringLiteral(t *testing.T) { + input := `"Hello World!"` + + evaluated := testEval(input) + str, ok := evaluated.(*object.String) + if !ok { + t.Fatalf("object is not String. got=%T (%+v)", evaluated, evaluated) + } + + if str.Value != "Hello World!" { + t.Errorf("String has wrong value. got=%q", str.Value) + } +} + +func TestStringConcatenation(t *testing.T) { + input := `"Hello" + " " + "World!"` + + evaluated := testEval(input) + str, ok := evaluated.(*object.String) + if !ok { + t.Fatalf("object is not String. got=%T (%+v)", evaluated, evaluated) + } + + if str.Value != "Hello World!" { + t.Errorf("String has wrong value. got=%q", str.Value) + } +} + +func TestBuiltinFunctions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + {`len("")`, 0}, + {`len("four")`, 4}, + {`len("hello world")`, 11}, + {`len(1)`, "argument to `len` not supported, got INTEGER"}, + {`len("one", "two")`, "wrong number of arguments. got=2, want=1"}, + {`len([1, 2, 3])`, 3}, + {`len([])`, 0}, + {`puts("hello", "world!")`, nil}, + {`first([1, 2, 3])`, 1}, + {`first([])`, nil}, + {`first(1)`, "argument to `first` must be ARRAY, got INTEGER"}, + {`last([1, 2, 3])`, 3}, + {`last([])`, nil}, + {`last(1)`, "argument to `last` must be ARRAY, got INTEGER"}, + {`rest([1, 2, 3])`, []int{2, 3}}, + {`rest([])`, nil}, + {`push([], 1)`, []int{1}}, + {`push(1, 1)`, "argument to `push` must be ARRAY, got INTEGER"}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + + switch expected := tt.expected.(type) { + case int: + testIntegerObject(t, evaluated, int64(expected)) + case nil: + testNullObject(t, evaluated) + case string: + errObj, ok := evaluated.(*object.Error) + if !ok { + t.Errorf("object is not Error. got=%T (%+v)", + evaluated, evaluated) + continue + } + if errObj.Message != expected { + t.Errorf("wrong error message. expected=%q, got=%q", + expected, errObj.Message) + } + case []int: + array, ok := evaluated.(*object.Array) + if !ok { + t.Errorf("obj not Array. got=%T (%+v)", evaluated, evaluated) + continue + } + + if len(array.Elements) != len(expected) { + t.Errorf("wrong num of elements. want=%d, got=%d", + len(expected), len(array.Elements)) + continue + } + + for i, expectedElem := range expected { + testIntegerObject(t, array.Elements[i], int64(expectedElem)) + } + } + } +} + +func TestArrayLiterals(t *testing.T) { + input := "[1, 2 * 2, 3 + 3]" + + evaluated := testEval(input) + result, ok := evaluated.(*object.Array) + if !ok { + t.Fatalf("object is not Array. got=%T (%+v)", evaluated, evaluated) + } + + if len(result.Elements) != 3 { + t.Fatalf("array has wrong num of elements. got=%d", + len(result.Elements)) + } + + testIntegerObject(t, result.Elements[0], 1) + testIntegerObject(t, result.Elements[1], 4) + testIntegerObject(t, result.Elements[2], 6) +} + +func TestArrayIndexExpressions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + { + "[1, 2, 3][0]", + 1, + }, + { + "[1, 2, 3][1]", + 2, + }, + { + "[1, 2, 3][2]", + 3, + }, + { + "let i = 0; [1][i];", + 1, + }, + { + "[1, 2, 3][1 + 1];", + 3, + }, + { + "let myArray = [1, 2, 3]; myArray[2];", + 3, + }, + { + "let myArray = [1, 2, 3]; myArray[0] + myArray[1] + myArray[2];", + 6, + }, + { + "let myArray = [1, 2, 3]; let i = myArray[0]; myArray[i]", + 2, + }, + { + "[1, 2, 3][3]", + nil, + }, + { + "[1, 2, 3][-1]", + nil, + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + integer, ok := tt.expected.(int) + if ok { + testIntegerObject(t, evaluated, int64(integer)) + } else { + testNullObject(t, evaluated) + } + } +} + +func TestHashLiterals(t *testing.T) { + input := `let two = "two"; + { + "one": 10 - 9, + two: 1 + 1, + "thr" + "ee": 6 / 2, + 4: 4, + true: 5, + false: 6 + }` + + evaluated := testEval(input) + result, ok := evaluated.(*object.Hash) + if !ok { + t.Fatalf("Eval didn't return Hash. got=%T (%+v)", evaluated, evaluated) + } + + expected := map[object.HashKey]int64{ + (&object.String{Value: "one"}).HashKey(): 1, + (&object.String{Value: "two"}).HashKey(): 2, + (&object.String{Value: "three"}).HashKey(): 3, + (&object.Integer{Value: 4}).HashKey(): 4, + TRUE.HashKey(): 5, + FALSE.HashKey(): 6, + } + + if len(result.Pairs) != len(expected) { + t.Fatalf("Hash has wrong num of pairs. got=%d", len(result.Pairs)) + } + + for expectedKey, expectedValue := range expected { + pair, ok := result.Pairs[expectedKey] + if !ok { + t.Errorf("no pair for given key in Pairs") + } + + testIntegerObject(t, pair.Value, expectedValue) + } +} + +func TestHashIndexExpressions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + { + `{"foo": 5}["foo"]`, + 5, + }, + { + `{"foo": 5}["bar"]`, + nil, + }, + { + `let key = "foo"; {"foo": 5}[key]`, + 5, + }, + { + `{}["foo"]`, + nil, + }, + { + `{5: 5}[5]`, + 5, + }, + { + `{true: 5}[true]`, + 5, + }, + { + `{false: 5}[false]`, + 5, + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + integer, ok := tt.expected.(int) + if ok { + testIntegerObject(t, evaluated, int64(integer)) + } else { + testNullObject(t, evaluated) + } + } +} +func testEval(input string) object.Object { + l := lexer.New(input) + p := parser.New(l) + program := p.ParseProgram() + env := object.NewEnvironment() + + return Eval(program, env) +} + +func testIntegerObject(t *testing.T, obj object.Object, expected int64) bool { + result, ok := obj.(*object.Integer) + if !ok { + t.Errorf("object is not Integer. got=%T (%+v)", obj, obj) + return false + } + if result.Value != expected { + t.Errorf("object has wrong value. got=%d, want=%d", + result.Value, expected) + return false + } + + return true +} + +func testBooleanObject(t *testing.T, obj object.Object, expected bool) bool { + result, ok := obj.(*object.Boolean) + if !ok { + t.Errorf("object is not Boolean. got=%T (%+v)", obj, obj) + return false + } + if result.Value != expected { + t.Errorf("object has wrong value. got=%t, want=%t", + result.Value, expected) + return false + } + return true +} + +func testNullObject(t *testing.T, obj object.Object) bool { + if obj != NULL { + t.Errorf("object is not NULL. got=%T (%+v)", obj, obj) + return false + } + return true +} diff --git a/wcig_code_1_2/04/src/monkey/go.mod b/wcig_code_1_2/04/src/monkey/go.mod new file mode 100644 index 0000000..3064854 --- /dev/null +++ b/wcig_code_1_2/04/src/monkey/go.mod @@ -0,0 +1,3 @@ +module monkey + +go 1.14 diff --git a/wcig_code_1_2/04/src/monkey/lexer/lexer.go b/wcig_code_1_2/04/src/monkey/lexer/lexer.go new file mode 100644 index 0000000..db4f5f7 --- /dev/null +++ b/wcig_code_1_2/04/src/monkey/lexer/lexer.go @@ -0,0 +1,157 @@ +package lexer + +import "monkey/token" + +type Lexer struct { + input string + position int // current position in input (points to current char) + readPosition int // current reading position in input (after current char) + ch byte // current char under examination +} + +func New(input string) *Lexer { + l := &Lexer{input: input} + l.readChar() + return l +} + +func (l *Lexer) NextToken() token.Token { + var tok token.Token + + l.skipWhitespace() + + switch l.ch { + case '=': + if l.peekChar() == '=' { + ch := l.ch + l.readChar() + literal := string(ch) + string(l.ch) + tok = token.Token{Type: token.EQ, Literal: literal} + } else { + tok = newToken(token.ASSIGN, l.ch) + } + case '+': + tok = newToken(token.PLUS, l.ch) + case '-': + tok = newToken(token.MINUS, l.ch) + case '!': + if l.peekChar() == '=' { + ch := l.ch + l.readChar() + literal := string(ch) + string(l.ch) + tok = token.Token{Type: token.NOT_EQ, Literal: literal} + } else { + tok = newToken(token.BANG, l.ch) + } + case '/': + tok = newToken(token.SLASH, l.ch) + case '*': + tok = newToken(token.ASTERISK, l.ch) + case '<': + tok = newToken(token.LT, l.ch) + case '>': + tok = newToken(token.GT, l.ch) + case ';': + tok = newToken(token.SEMICOLON, l.ch) + case ':': + tok = newToken(token.COLON, l.ch) + case ',': + tok = newToken(token.COMMA, l.ch) + case '{': + tok = newToken(token.LBRACE, l.ch) + case '}': + tok = newToken(token.RBRACE, l.ch) + case '(': + tok = newToken(token.LPAREN, l.ch) + case ')': + tok = newToken(token.RPAREN, l.ch) + case '"': + tok.Type = token.STRING + tok.Literal = l.readString() + case '[': + tok = newToken(token.LBRACKET, l.ch) + case ']': + tok = newToken(token.RBRACKET, l.ch) + case 0: + tok.Literal = "" + tok.Type = token.EOF + default: + if isLetter(l.ch) { + tok.Literal = l.readIdentifier() + tok.Type = token.LookupIdent(tok.Literal) + return tok + } else if isDigit(l.ch) { + tok.Type = token.INT + tok.Literal = l.readNumber() + return tok + } else { + tok = newToken(token.ILLEGAL, l.ch) + } + } + + l.readChar() + return tok +} + +func (l *Lexer) skipWhitespace() { + for l.ch == ' ' || l.ch == '\t' || l.ch == '\n' || l.ch == '\r' { + l.readChar() + } +} + +func (l *Lexer) readChar() { + if l.readPosition >= len(l.input) { + l.ch = 0 + } else { + l.ch = l.input[l.readPosition] + } + l.position = l.readPosition + l.readPosition += 1 +} + +func (l *Lexer) peekChar() byte { + if l.readPosition >= len(l.input) { + return 0 + } else { + return l.input[l.readPosition] + } +} + +func (l *Lexer) readIdentifier() string { + position := l.position + for isLetter(l.ch) { + l.readChar() + } + return l.input[position:l.position] +} + +func (l *Lexer) readNumber() string { + position := l.position + for isDigit(l.ch) { + l.readChar() + } + return l.input[position:l.position] +} + +func (l *Lexer) readString() string { + position := l.position + 1 + for { + l.readChar() + if l.ch == '"' || l.ch == 0 { + break + } + } + return l.input[position:l.position] +} + +func isLetter(ch byte) bool { + return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' +} + +func isDigit(ch byte) bool { + return '0' <= ch && ch <= '9' +} + +func newToken(tokenType token.TokenType, ch byte) token.Token { + return token.Token{Type: tokenType, Literal: string(ch)} +} diff --git a/wcig_code_1_2/04/src/monkey/lexer/lexer_test.go b/wcig_code_1_2/04/src/monkey/lexer/lexer_test.go new file mode 100644 index 0000000..e4e64a4 --- /dev/null +++ b/wcig_code_1_2/04/src/monkey/lexer/lexer_test.go @@ -0,0 +1,143 @@ +package lexer + +import ( + "testing" + + "monkey/token" +) + +func TestNextToken(t *testing.T) { + input := `let five = 5; +let ten = 10; + +let add = fn(x, y) { + x + y; +}; + +let result = add(five, ten); +!-/*5; +5 < 10 > 5; + +if (5 < 10) { + return true; +} else { + return false; +} + +10 == 10; +10 != 9; +"foobar" +"foo bar" +[1, 2]; +{"foo": "bar"} +` + + tests := []struct { + expectedType token.TokenType + expectedLiteral string + }{ + {token.LET, "let"}, + {token.IDENT, "five"}, + {token.ASSIGN, "="}, + {token.INT, "5"}, + {token.SEMICOLON, ";"}, + {token.LET, "let"}, + {token.IDENT, "ten"}, + {token.ASSIGN, "="}, + {token.INT, "10"}, + {token.SEMICOLON, ";"}, + {token.LET, "let"}, + {token.IDENT, "add"}, + {token.ASSIGN, "="}, + {token.FUNCTION, "fn"}, + {token.LPAREN, "("}, + {token.IDENT, "x"}, + {token.COMMA, ","}, + {token.IDENT, "y"}, + {token.RPAREN, ")"}, + {token.LBRACE, "{"}, + {token.IDENT, "x"}, + {token.PLUS, "+"}, + {token.IDENT, "y"}, + {token.SEMICOLON, ";"}, + {token.RBRACE, "}"}, + {token.SEMICOLON, ";"}, + {token.LET, "let"}, + {token.IDENT, "result"}, + {token.ASSIGN, "="}, + {token.IDENT, "add"}, + {token.LPAREN, "("}, + {token.IDENT, "five"}, + {token.COMMA, ","}, + {token.IDENT, "ten"}, + {token.RPAREN, ")"}, + {token.SEMICOLON, ";"}, + {token.BANG, "!"}, + {token.MINUS, "-"}, + {token.SLASH, "/"}, + {token.ASTERISK, "*"}, + {token.INT, "5"}, + {token.SEMICOLON, ";"}, + {token.INT, "5"}, + {token.LT, "<"}, + {token.INT, "10"}, + {token.GT, ">"}, + {token.INT, "5"}, + {token.SEMICOLON, ";"}, + {token.IF, "if"}, + {token.LPAREN, "("}, + {token.INT, "5"}, + {token.LT, "<"}, + {token.INT, "10"}, + {token.RPAREN, ")"}, + {token.LBRACE, "{"}, + {token.RETURN, "return"}, + {token.TRUE, "true"}, + {token.SEMICOLON, ";"}, + {token.RBRACE, "}"}, + {token.ELSE, "else"}, + {token.LBRACE, "{"}, + {token.RETURN, "return"}, + {token.FALSE, "false"}, + {token.SEMICOLON, ";"}, + {token.RBRACE, "}"}, + {token.INT, "10"}, + {token.EQ, "=="}, + {token.INT, "10"}, + {token.SEMICOLON, ";"}, + {token.INT, "10"}, + {token.NOT_EQ, "!="}, + {token.INT, "9"}, + {token.SEMICOLON, ";"}, + {token.STRING, "foobar"}, + {token.STRING, "foo bar"}, + {token.LBRACKET, "["}, + {token.INT, "1"}, + {token.COMMA, ","}, + {token.INT, "2"}, + {token.RBRACKET, "]"}, + {token.SEMICOLON, ";"}, + {token.LBRACE, "{"}, + {token.STRING, "foo"}, + {token.COLON, ":"}, + {token.STRING, "bar"}, + {token.RBRACE, "}"}, + {token.EOF, ""}, + } + + l := New(input) + + for i, tt := range tests { + tok := l.NextToken() + + if tok.Type != tt.expectedType { + t.Fatalf("tests[%d] - tokentype wrong. expected=%q, got=%q", + i, tt.expectedType, tok.Type) + } + + if tok.Literal != tt.expectedLiteral { + t.Fatalf("tests[%d] - literal wrong. expected=%q, got=%q", + i, tt.expectedLiteral, tok.Literal) + } + } +} diff --git a/wcig_code_1_2/04/src/monkey/main.go b/wcig_code_1_2/04/src/monkey/main.go new file mode 100644 index 0000000..1d27138 --- /dev/null +++ b/wcig_code_1_2/04/src/monkey/main.go @@ -0,0 +1,19 @@ +package main + +import ( + "fmt" + "monkey/repl" + "os" + "os/user" +) + +func main() { + user, err := user.Current() + if err != nil { + panic(err) + } + fmt.Printf("Hello %s! This is the Monkey programming language!\n", + user.Username) + fmt.Printf("Feel free to type in commands\n") + repl.Start(os.Stdin, os.Stdout) +} diff --git a/wcig_code_1_2/04/src/monkey/object/environment.go b/wcig_code_1_2/04/src/monkey/object/environment.go new file mode 100644 index 0000000..6f31070 --- /dev/null +++ b/wcig_code_1_2/04/src/monkey/object/environment.go @@ -0,0 +1,30 @@ +package object + +func NewEnclosedEnvironment(outer *Environment) *Environment { + env := NewEnvironment() + env.outer = outer + return env +} + +func NewEnvironment() *Environment { + s := make(map[string]Object) + return &Environment{store: s, outer: nil} +} + +type Environment struct { + store map[string]Object + outer *Environment +} + +func (e *Environment) Get(name string) (Object, bool) { + obj, ok := e.store[name] + if !ok && e.outer != nil { + obj, ok = e.outer.Get(name) + } + return obj, ok +} + +func (e *Environment) Set(name string, val Object) Object { + e.store[name] = val + return val +} diff --git a/wcig_code_1_2/04/src/monkey/object/object.go b/wcig_code_1_2/04/src/monkey/object/object.go new file mode 100644 index 0000000..2c2a1b0 --- /dev/null +++ b/wcig_code_1_2/04/src/monkey/object/object.go @@ -0,0 +1,182 @@ +package object + +import ( + "bytes" + "fmt" + "hash/fnv" + "monkey/ast" + "strings" +) + +type BuiltinFunction func(args ...Object) Object + +type ObjectType string + +const ( + NULL_OBJ = "NULL" + ERROR_OBJ = "ERROR" + + INTEGER_OBJ = "INTEGER" + BOOLEAN_OBJ = "BOOLEAN" + STRING_OBJ = "STRING" + + RETURN_VALUE_OBJ = "RETURN_VALUE" + + FUNCTION_OBJ = "FUNCTION" + BUILTIN_OBJ = "BUILTIN" + + ARRAY_OBJ = "ARRAY" + HASH_OBJ = "HASH" +) + +type HashKey struct { + Type ObjectType + Value uint64 +} + +type Hashable interface { + HashKey() HashKey +} + +type Object interface { + Type() ObjectType + Inspect() string +} + +type Integer struct { + Value int64 +} + +func (i *Integer) Type() ObjectType { return INTEGER_OBJ } +func (i *Integer) Inspect() string { return fmt.Sprintf("%d", i.Value) } +func (i *Integer) HashKey() HashKey { + return HashKey{Type: i.Type(), Value: uint64(i.Value)} +} + +type Boolean struct { + Value bool +} + +func (b *Boolean) Type() ObjectType { return BOOLEAN_OBJ } +func (b *Boolean) Inspect() string { return fmt.Sprintf("%t", b.Value) } +func (b *Boolean) HashKey() HashKey { + var value uint64 + + if b.Value { + value = 1 + } else { + value = 0 + } + + return HashKey{Type: b.Type(), Value: value} +} + +type Null struct{} + +func (n *Null) Type() ObjectType { return NULL_OBJ } +func (n *Null) Inspect() string { return "null" } + +type ReturnValue struct { + Value Object +} + +func (rv *ReturnValue) Type() ObjectType { return RETURN_VALUE_OBJ } +func (rv *ReturnValue) Inspect() string { return rv.Value.Inspect() } + +type Error struct { + Message string +} + +func (e *Error) Type() ObjectType { return ERROR_OBJ } +func (e *Error) Inspect() string { return "ERROR: " + e.Message } + +type Function struct { + Parameters []*ast.Identifier + Body *ast.BlockStatement + Env *Environment +} + +func (f *Function) Type() ObjectType { return FUNCTION_OBJ } +func (f *Function) Inspect() string { + var out bytes.Buffer + + params := []string{} + for _, p := range f.Parameters { + params = append(params, p.String()) + } + + out.WriteString("fn") + out.WriteString("(") + out.WriteString(strings.Join(params, ", ")) + out.WriteString(") {\n") + out.WriteString(f.Body.String()) + out.WriteString("\n}") + + return out.String() +} + +type String struct { + Value string +} + +func (s *String) Type() ObjectType { return STRING_OBJ } +func (s *String) Inspect() string { return s.Value } +func (s *String) HashKey() HashKey { + h := fnv.New64a() + h.Write([]byte(s.Value)) + + return HashKey{Type: s.Type(), Value: h.Sum64()} +} + +type Builtin struct { + Fn BuiltinFunction +} + +func (b *Builtin) Type() ObjectType { return BUILTIN_OBJ } +func (b *Builtin) Inspect() string { return "builtin function" } + +type Array struct { + Elements []Object +} + +func (ao *Array) Type() ObjectType { return ARRAY_OBJ } +func (ao *Array) Inspect() string { + var out bytes.Buffer + + elements := []string{} + for _, e := range ao.Elements { + elements = append(elements, e.Inspect()) + } + + out.WriteString("[") + out.WriteString(strings.Join(elements, ", ")) + out.WriteString("]") + + return out.String() +} + +type HashPair struct { + Key Object + Value Object +} + +type Hash struct { + Pairs map[HashKey]HashPair +} + +func (h *Hash) Type() ObjectType { return HASH_OBJ } +func (h *Hash) Inspect() string { + var out bytes.Buffer + + pairs := []string{} + for _, pair := range h.Pairs { + pairs = append(pairs, fmt.Sprintf("%s: %s", + pair.Key.Inspect(), pair.Value.Inspect())) + } + + out.WriteString("{") + out.WriteString(strings.Join(pairs, ", ")) + out.WriteString("}") + + return out.String() +} diff --git a/wcig_code_1_2/04/src/monkey/object/object_test.go b/wcig_code_1_2/04/src/monkey/object/object_test.go new file mode 100644 index 0000000..63228a2 --- /dev/null +++ b/wcig_code_1_2/04/src/monkey/object/object_test.go @@ -0,0 +1,60 @@ +package object + +import "testing" + +func TestStringHashKey(t *testing.T) { + hello1 := &String{Value: "Hello World"} + hello2 := &String{Value: "Hello World"} + diff1 := &String{Value: "My name is johnny"} + diff2 := &String{Value: "My name is johnny"} + + if hello1.HashKey() != hello2.HashKey() { + t.Errorf("strings with same content have different hash keys") + } + + if diff1.HashKey() != diff2.HashKey() { + t.Errorf("strings with same content have different hash keys") + } + + if hello1.HashKey() == diff1.HashKey() { + t.Errorf("strings with different content have same hash keys") + } +} + +func TestBooleanHashKey(t *testing.T) { + true1 := &Boolean{Value: true} + true2 := &Boolean{Value: true} + false1 := &Boolean{Value: false} + false2 := &Boolean{Value: false} + + if true1.HashKey() != true2.HashKey() { + t.Errorf("trues do not have same hash key") + } + + if false1.HashKey() != false2.HashKey() { + t.Errorf("falses do not have same hash key") + } + + if true1.HashKey() == false1.HashKey() { + t.Errorf("true has same hash key as false") + } +} + +func TestIntegerHashKey(t *testing.T) { + one1 := &Integer{Value: 1} + one2 := &Integer{Value: 1} + two1 := &Integer{Value: 2} + two2 := &Integer{Value: 2} + + if one1.HashKey() != one2.HashKey() { + t.Errorf("integers with same content have twoerent hash keys") + } + + if two1.HashKey() != two2.HashKey() { + t.Errorf("integers with same content have twoerent hash keys") + } + + if one1.HashKey() == two1.HashKey() { + t.Errorf("integers with twoerent content have same hash keys") + } +} diff --git a/wcig_code_1_2/04/src/monkey/parser/parser.go b/wcig_code_1_2/04/src/monkey/parser/parser.go new file mode 100644 index 0000000..0a1f19c --- /dev/null +++ b/wcig_code_1_2/04/src/monkey/parser/parser.go @@ -0,0 +1,491 @@ +package parser + +import ( + "fmt" + "monkey/ast" + "monkey/lexer" + "monkey/token" + "strconv" +) + +const ( + _ int = iota + LOWEST + EQUALS // == + LESSGREATER // > or < + SUM // + + PRODUCT // * + PREFIX // -X or !X + CALL // myFunction(X) + INDEX // array[index] +) + +var precedences = map[token.TokenType]int{ + token.EQ: EQUALS, + token.NOT_EQ: EQUALS, + token.LT: LESSGREATER, + token.GT: LESSGREATER, + token.PLUS: SUM, + token.MINUS: SUM, + token.SLASH: PRODUCT, + token.ASTERISK: PRODUCT, + token.LPAREN: CALL, + token.LBRACKET: INDEX, +} + +type ( + prefixParseFn func() ast.Expression + infixParseFn func(ast.Expression) ast.Expression +) + +type Parser struct { + l *lexer.Lexer + errors []string + + curToken token.Token + peekToken token.Token + + prefixParseFns map[token.TokenType]prefixParseFn + infixParseFns map[token.TokenType]infixParseFn +} + +func New(l *lexer.Lexer) *Parser { + p := &Parser{ + l: l, + errors: []string{}, + } + + p.prefixParseFns = make(map[token.TokenType]prefixParseFn) + p.registerPrefix(token.IDENT, p.parseIdentifier) + p.registerPrefix(token.INT, p.parseIntegerLiteral) + p.registerPrefix(token.STRING, p.parseStringLiteral) + p.registerPrefix(token.BANG, p.parsePrefixExpression) + p.registerPrefix(token.MINUS, p.parsePrefixExpression) + p.registerPrefix(token.TRUE, p.parseBoolean) + p.registerPrefix(token.FALSE, p.parseBoolean) + p.registerPrefix(token.LPAREN, p.parseGroupedExpression) + p.registerPrefix(token.IF, p.parseIfExpression) + p.registerPrefix(token.FUNCTION, p.parseFunctionLiteral) + p.registerPrefix(token.LBRACKET, p.parseArrayLiteral) + p.registerPrefix(token.LBRACE, p.parseHashLiteral) + + p.infixParseFns = make(map[token.TokenType]infixParseFn) + p.registerInfix(token.PLUS, p.parseInfixExpression) + p.registerInfix(token.MINUS, p.parseInfixExpression) + p.registerInfix(token.SLASH, p.parseInfixExpression) + p.registerInfix(token.ASTERISK, p.parseInfixExpression) + p.registerInfix(token.EQ, p.parseInfixExpression) + p.registerInfix(token.NOT_EQ, p.parseInfixExpression) + p.registerInfix(token.LT, p.parseInfixExpression) + p.registerInfix(token.GT, p.parseInfixExpression) + + p.registerInfix(token.LPAREN, p.parseCallExpression) + p.registerInfix(token.LBRACKET, p.parseIndexExpression) + + // Read two tokens, so curToken and peekToken are both set + p.nextToken() + p.nextToken() + + return p +} + +func (p *Parser) nextToken() { + p.curToken = p.peekToken + p.peekToken = p.l.NextToken() +} + +func (p *Parser) curTokenIs(t token.TokenType) bool { + return p.curToken.Type == t +} + +func (p *Parser) peekTokenIs(t token.TokenType) bool { + return p.peekToken.Type == t +} + +func (p *Parser) expectPeek(t token.TokenType) bool { + if p.peekTokenIs(t) { + p.nextToken() + return true + } else { + p.peekError(t) + return false + } +} + +func (p *Parser) Errors() []string { + return p.errors +} + +func (p *Parser) peekError(t token.TokenType) { + msg := fmt.Sprintf("expected next token to be %s, got %s instead", + t, p.peekToken.Type) + p.errors = append(p.errors, msg) +} + +func (p *Parser) noPrefixParseFnError(t token.TokenType) { + msg := fmt.Sprintf("no prefix parse function for %s found", t) + p.errors = append(p.errors, msg) +} + +func (p *Parser) ParseProgram() *ast.Program { + program := &ast.Program{} + program.Statements = []ast.Statement{} + + for !p.curTokenIs(token.EOF) { + stmt := p.parseStatement() + if stmt != nil { + program.Statements = append(program.Statements, stmt) + } + p.nextToken() + } + + return program +} + +func (p *Parser) parseStatement() ast.Statement { + switch p.curToken.Type { + case token.LET: + return p.parseLetStatement() + case token.RETURN: + return p.parseReturnStatement() + default: + return p.parseExpressionStatement() + } +} + +func (p *Parser) parseLetStatement() *ast.LetStatement { + stmt := &ast.LetStatement{Token: p.curToken} + + if !p.expectPeek(token.IDENT) { + return nil + } + + stmt.Name = &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + + if !p.expectPeek(token.ASSIGN) { + return nil + } + + p.nextToken() + + stmt.Value = p.parseExpression(LOWEST) + + if p.peekTokenIs(token.SEMICOLON) { + p.nextToken() + } + + return stmt +} + +func (p *Parser) parseReturnStatement() *ast.ReturnStatement { + stmt := &ast.ReturnStatement{Token: p.curToken} + + p.nextToken() + + stmt.ReturnValue = p.parseExpression(LOWEST) + + if p.peekTokenIs(token.SEMICOLON) { + p.nextToken() + } + + return stmt +} + +func (p *Parser) parseExpressionStatement() *ast.ExpressionStatement { + stmt := &ast.ExpressionStatement{Token: p.curToken} + + stmt.Expression = p.parseExpression(LOWEST) + + if p.peekTokenIs(token.SEMICOLON) { + p.nextToken() + } + + return stmt +} + +func (p *Parser) parseExpression(precedence int) ast.Expression { + prefix := p.prefixParseFns[p.curToken.Type] + if prefix == nil { + p.noPrefixParseFnError(p.curToken.Type) + return nil + } + leftExp := prefix() + + for !p.peekTokenIs(token.SEMICOLON) && precedence < p.peekPrecedence() { + infix := p.infixParseFns[p.peekToken.Type] + if infix == nil { + return leftExp + } + + p.nextToken() + + leftExp = infix(leftExp) + } + + return leftExp +} + +func (p *Parser) peekPrecedence() int { + if p, ok := precedences[p.peekToken.Type]; ok { + return p + } + + return LOWEST +} + +func (p *Parser) curPrecedence() int { + if p, ok := precedences[p.curToken.Type]; ok { + return p + } + + return LOWEST +} + +func (p *Parser) parseIdentifier() ast.Expression { + return &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} +} + +func (p *Parser) parseIntegerLiteral() ast.Expression { + lit := &ast.IntegerLiteral{Token: p.curToken} + + value, err := strconv.ParseInt(p.curToken.Literal, 0, 64) + if err != nil { + msg := fmt.Sprintf("could not parse %q as integer", p.curToken.Literal) + p.errors = append(p.errors, msg) + return nil + } + + lit.Value = value + + return lit +} + +func (p *Parser) parseStringLiteral() ast.Expression { + return &ast.StringLiteral{Token: p.curToken, Value: p.curToken.Literal} +} + +func (p *Parser) parsePrefixExpression() ast.Expression { + expression := &ast.PrefixExpression{ + Token: p.curToken, + Operator: p.curToken.Literal, + } + + p.nextToken() + + expression.Right = p.parseExpression(PREFIX) + + return expression +} + +func (p *Parser) parseInfixExpression(left ast.Expression) ast.Expression { + expression := &ast.InfixExpression{ + Token: p.curToken, + Operator: p.curToken.Literal, + Left: left, + } + + precedence := p.curPrecedence() + p.nextToken() + expression.Right = p.parseExpression(precedence) + + return expression +} + +func (p *Parser) parseBoolean() ast.Expression { + return &ast.Boolean{Token: p.curToken, Value: p.curTokenIs(token.TRUE)} +} + +func (p *Parser) parseGroupedExpression() ast.Expression { + p.nextToken() + + exp := p.parseExpression(LOWEST) + + if !p.expectPeek(token.RPAREN) { + return nil + } + + return exp +} + +func (p *Parser) parseIfExpression() ast.Expression { + expression := &ast.IfExpression{Token: p.curToken} + + if !p.expectPeek(token.LPAREN) { + return nil + } + + p.nextToken() + expression.Condition = p.parseExpression(LOWEST) + + if !p.expectPeek(token.RPAREN) { + return nil + } + + if !p.expectPeek(token.LBRACE) { + return nil + } + + expression.Consequence = p.parseBlockStatement() + + if p.peekTokenIs(token.ELSE) { + p.nextToken() + + if !p.expectPeek(token.LBRACE) { + return nil + } + + expression.Alternative = p.parseBlockStatement() + } + + return expression +} + +func (p *Parser) parseBlockStatement() *ast.BlockStatement { + block := &ast.BlockStatement{Token: p.curToken} + block.Statements = []ast.Statement{} + + p.nextToken() + + for !p.curTokenIs(token.RBRACE) && !p.curTokenIs(token.EOF) { + stmt := p.parseStatement() + if stmt != nil { + block.Statements = append(block.Statements, stmt) + } + p.nextToken() + } + + return block +} + +func (p *Parser) parseFunctionLiteral() ast.Expression { + lit := &ast.FunctionLiteral{Token: p.curToken} + + if !p.expectPeek(token.LPAREN) { + return nil + } + + lit.Parameters = p.parseFunctionParameters() + + if !p.expectPeek(token.LBRACE) { + return nil + } + + lit.Body = p.parseBlockStatement() + + return lit +} + +func (p *Parser) parseFunctionParameters() []*ast.Identifier { + identifiers := []*ast.Identifier{} + + if p.peekTokenIs(token.RPAREN) { + p.nextToken() + return identifiers + } + + p.nextToken() + + ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + identifiers = append(identifiers, ident) + + for p.peekTokenIs(token.COMMA) { + p.nextToken() + p.nextToken() + ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + identifiers = append(identifiers, ident) + } + + if !p.expectPeek(token.RPAREN) { + return nil + } + + return identifiers +} + +func (p *Parser) parseCallExpression(function ast.Expression) ast.Expression { + exp := &ast.CallExpression{Token: p.curToken, Function: function} + exp.Arguments = p.parseExpressionList(token.RPAREN) + return exp +} + +func (p *Parser) parseExpressionList(end token.TokenType) []ast.Expression { + list := []ast.Expression{} + + if p.peekTokenIs(end) { + p.nextToken() + return list + } + + p.nextToken() + list = append(list, p.parseExpression(LOWEST)) + + for p.peekTokenIs(token.COMMA) { + p.nextToken() + p.nextToken() + list = append(list, p.parseExpression(LOWEST)) + } + + if !p.expectPeek(end) { + return nil + } + + return list +} + +func (p *Parser) parseArrayLiteral() ast.Expression { + array := &ast.ArrayLiteral{Token: p.curToken} + + array.Elements = p.parseExpressionList(token.RBRACKET) + + return array +} + +func (p *Parser) parseIndexExpression(left ast.Expression) ast.Expression { + exp := &ast.IndexExpression{Token: p.curToken, Left: left} + + p.nextToken() + exp.Index = p.parseExpression(LOWEST) + + if !p.expectPeek(token.RBRACKET) { + return nil + } + + return exp +} + +func (p *Parser) parseHashLiteral() ast.Expression { + hash := &ast.HashLiteral{Token: p.curToken} + hash.Pairs = make(map[ast.Expression]ast.Expression) + + for !p.peekTokenIs(token.RBRACE) { + p.nextToken() + key := p.parseExpression(LOWEST) + + if !p.expectPeek(token.COLON) { + return nil + } + + p.nextToken() + value := p.parseExpression(LOWEST) + + hash.Pairs[key] = value + + if !p.peekTokenIs(token.RBRACE) && !p.expectPeek(token.COMMA) { + return nil + } + } + + if !p.expectPeek(token.RBRACE) { + return nil + } + + return hash +} + +func (p *Parser) registerPrefix(tokenType token.TokenType, fn prefixParseFn) { + p.prefixParseFns[tokenType] = fn +} + +func (p *Parser) registerInfix(tokenType token.TokenType, fn infixParseFn) { + p.infixParseFns[tokenType] = fn +} diff --git a/wcig_code_1_2/04/src/monkey/parser/parser_test.go b/wcig_code_1_2/04/src/monkey/parser/parser_test.go new file mode 100644 index 0000000..674487d --- /dev/null +++ b/wcig_code_1_2/04/src/monkey/parser/parser_test.go @@ -0,0 +1,1083 @@ +package parser + +import ( + "fmt" + "monkey/ast" + "monkey/lexer" + "testing" +) + +func TestLetStatements(t *testing.T) { + tests := []struct { + input string + expectedIdentifier string + expectedValue interface{} + }{ + {"let x = 5;", "x", 5}, + {"let y = true;", "y", true}, + {"let foobar = y;", "foobar", "y"}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain 1 statements. got=%d", + len(program.Statements)) + } + + stmt := program.Statements[0] + if !testLetStatement(t, stmt, tt.expectedIdentifier) { + return + } + + val := stmt.(*ast.LetStatement).Value + if !testLiteralExpression(t, val, tt.expectedValue) { + return + } + } +} + +func TestReturnStatements(t *testing.T) { + tests := []struct { + input string + expectedValue interface{} + }{ + {"return 5;", 5}, + {"return true;", true}, + {"return foobar;", "foobar"}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain 1 statements. got=%d", + len(program.Statements)) + } + + stmt := program.Statements[0] + returnStmt, ok := stmt.(*ast.ReturnStatement) + if !ok { + t.Fatalf("stmt not *ast.returnStatement. got=%T", stmt) + } + if returnStmt.TokenLiteral() != "return" { + t.Fatalf("returnStmt.TokenLiteral not 'return', got %q", + returnStmt.TokenLiteral()) + } + if testLiteralExpression(t, returnStmt.ReturnValue, tt.expectedValue) { + return + } + } +} + +func TestIdentifierExpression(t *testing.T) { + input := "foobar;" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program has not enough statements. got=%d", + len(program.Statements)) + } + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + ident, ok := stmt.Expression.(*ast.Identifier) + if !ok { + t.Fatalf("exp not *ast.Identifier. got=%T", stmt.Expression) + } + if ident.Value != "foobar" { + t.Errorf("ident.Value not %s. got=%s", "foobar", ident.Value) + } + if ident.TokenLiteral() != "foobar" { + t.Errorf("ident.TokenLiteral not %s. got=%s", "foobar", + ident.TokenLiteral()) + } +} + +func TestIntegerLiteralExpression(t *testing.T) { + input := "5;" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program has not enough statements. got=%d", + len(program.Statements)) + } + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + literal, ok := stmt.Expression.(*ast.IntegerLiteral) + if !ok { + t.Fatalf("exp not *ast.IntegerLiteral. got=%T", stmt.Expression) + } + if literal.Value != 5 { + t.Errorf("literal.Value not %d. got=%d", 5, literal.Value) + } + if literal.TokenLiteral() != "5" { + t.Errorf("literal.TokenLiteral not %s. got=%s", "5", + literal.TokenLiteral()) + } +} + +func TestParsingPrefixExpressions(t *testing.T) { + prefixTests := []struct { + input string + operator string + value interface{} + }{ + {"!5;", "!", 5}, + {"-15;", "-", 15}, + {"!foobar;", "!", "foobar"}, + {"-foobar;", "-", "foobar"}, + {"!true;", "!", true}, + {"!false;", "!", false}, + } + + for _, tt := range prefixTests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.PrefixExpression) + if !ok { + t.Fatalf("stmt is not ast.PrefixExpression. got=%T", stmt.Expression) + } + if exp.Operator != tt.operator { + t.Fatalf("exp.Operator is not '%s'. got=%s", + tt.operator, exp.Operator) + } + if !testLiteralExpression(t, exp.Right, tt.value) { + return + } + } +} + +func TestParsingInfixExpressions(t *testing.T) { + infixTests := []struct { + input string + leftValue interface{} + operator string + rightValue interface{} + }{ + {"5 + 5;", 5, "+", 5}, + {"5 - 5;", 5, "-", 5}, + {"5 * 5;", 5, "*", 5}, + {"5 / 5;", 5, "/", 5}, + {"5 > 5;", 5, ">", 5}, + {"5 < 5;", 5, "<", 5}, + {"5 == 5;", 5, "==", 5}, + {"5 != 5;", 5, "!=", 5}, + {"foobar + barfoo;", "foobar", "+", "barfoo"}, + {"foobar - barfoo;", "foobar", "-", "barfoo"}, + {"foobar * barfoo;", "foobar", "*", "barfoo"}, + {"foobar / barfoo;", "foobar", "/", "barfoo"}, + {"foobar > barfoo;", "foobar", ">", "barfoo"}, + {"foobar < barfoo;", "foobar", "<", "barfoo"}, + {"foobar == barfoo;", "foobar", "==", "barfoo"}, + {"foobar != barfoo;", "foobar", "!=", "barfoo"}, + {"true == true", true, "==", true}, + {"true != false", true, "!=", false}, + {"false == false", false, "==", false}, + } + + for _, tt := range infixTests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + if !testInfixExpression(t, stmt.Expression, tt.leftValue, + tt.operator, tt.rightValue) { + return + } + } +} + +func TestOperatorPrecedenceParsing(t *testing.T) { + tests := []struct { + input string + expected string + }{ + { + "-a * b", + "((-a) * b)", + }, + { + "!-a", + "(!(-a))", + }, + { + "a + b + c", + "((a + b) + c)", + }, + { + "a + b - c", + "((a + b) - c)", + }, + { + "a * b * c", + "((a * b) * c)", + }, + { + "a * b / c", + "((a * b) / c)", + }, + { + "a + b / c", + "(a + (b / c))", + }, + { + "a + b * c + d / e - f", + "(((a + (b * c)) + (d / e)) - f)", + }, + { + "3 + 4; -5 * 5", + "(3 + 4)((-5) * 5)", + }, + { + "5 > 4 == 3 < 4", + "((5 > 4) == (3 < 4))", + }, + { + "5 < 4 != 3 > 4", + "((5 < 4) != (3 > 4))", + }, + { + "3 + 4 * 5 == 3 * 1 + 4 * 5", + "((3 + (4 * 5)) == ((3 * 1) + (4 * 5)))", + }, + { + "true", + "true", + }, + { + "false", + "false", + }, + { + "3 > 5 == false", + "((3 > 5) == false)", + }, + { + "3 < 5 == true", + "((3 < 5) == true)", + }, + { + "1 + (2 + 3) + 4", + "((1 + (2 + 3)) + 4)", + }, + { + "(5 + 5) * 2", + "((5 + 5) * 2)", + }, + { + "2 / (5 + 5)", + "(2 / (5 + 5))", + }, + { + "(5 + 5) * 2 * (5 + 5)", + "(((5 + 5) * 2) * (5 + 5))", + }, + { + "-(5 + 5)", + "(-(5 + 5))", + }, + { + "!(true == true)", + "(!(true == true))", + }, + { + "a + add(b * c) + d", + "((a + add((b * c))) + d)", + }, + { + "add(a, b, 1, 2 * 3, 4 + 5, add(6, 7 * 8))", + "add(a, b, 1, (2 * 3), (4 + 5), add(6, (7 * 8)))", + }, + { + "add(a + b + c * d / f + g)", + "add((((a + b) + ((c * d) / f)) + g))", + }, + { + "a * [1, 2, 3, 4][b * c] * d", + "((a * ([1, 2, 3, 4][(b * c)])) * d)", + }, + { + "add(a * b[2], b[1], 2 * [1, 2][1])", + "add((a * (b[2])), (b[1]), (2 * ([1, 2][1])))", + }, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + actual := program.String() + if actual != tt.expected { + t.Errorf("expected=%q, got=%q", tt.expected, actual) + } + } +} + +func TestBooleanExpression(t *testing.T) { + tests := []struct { + input string + expectedBoolean bool + }{ + {"true;", true}, + {"false;", false}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program has not enough statements. got=%d", + len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + boolean, ok := stmt.Expression.(*ast.Boolean) + if !ok { + t.Fatalf("exp not *ast.Boolean. got=%T", stmt.Expression) + } + if boolean.Value != tt.expectedBoolean { + t.Errorf("boolean.Value not %t. got=%t", tt.expectedBoolean, + boolean.Value) + } + } +} + +func TestIfExpression(t *testing.T) { + input := `if (x < y) { x }` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.IfExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.IfExpression. got=%T", + stmt.Expression) + } + + if !testInfixExpression(t, exp.Condition, "x", "<", "y") { + return + } + + if len(exp.Consequence.Statements) != 1 { + t.Errorf("consequence is not 1 statements. got=%d\n", + len(exp.Consequence.Statements)) + } + + consequence, ok := exp.Consequence.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", + exp.Consequence.Statements[0]) + } + + if !testIdentifier(t, consequence.Expression, "x") { + return + } + + if exp.Alternative != nil { + t.Errorf("exp.Alternative.Statements was not nil. got=%+v", exp.Alternative) + } +} + +func TestIfElseExpression(t *testing.T) { + input := `if (x < y) { x } else { y }` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.IfExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.IfExpression. got=%T", stmt.Expression) + } + + if !testInfixExpression(t, exp.Condition, "x", "<", "y") { + return + } + + if len(exp.Consequence.Statements) != 1 { + t.Errorf("consequence is not 1 statements. got=%d\n", + len(exp.Consequence.Statements)) + } + + consequence, ok := exp.Consequence.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", + exp.Consequence.Statements[0]) + } + + if !testIdentifier(t, consequence.Expression, "x") { + return + } + + if len(exp.Alternative.Statements) != 1 { + t.Errorf("exp.Alternative.Statements does not contain 1 statements. got=%d\n", + len(exp.Alternative.Statements)) + } + + alternative, ok := exp.Alternative.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", + exp.Alternative.Statements[0]) + } + + if !testIdentifier(t, alternative.Expression, "y") { + return + } +} + +func TestFunctionLiteralParsing(t *testing.T) { + input := `fn(x, y) { x + y; }` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + function, ok := stmt.Expression.(*ast.FunctionLiteral) + if !ok { + t.Fatalf("stmt.Expression is not ast.FunctionLiteral. got=%T", + stmt.Expression) + } + + if len(function.Parameters) != 2 { + t.Fatalf("function literal parameters wrong. want 2, got=%d\n", + len(function.Parameters)) + } + + testLiteralExpression(t, function.Parameters[0], "x") + testLiteralExpression(t, function.Parameters[1], "y") + + if len(function.Body.Statements) != 1 { + t.Fatalf("function.Body.Statements has not 1 statements. got=%d\n", + len(function.Body.Statements)) + } + + bodyStmt, ok := function.Body.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("function body stmt is not ast.ExpressionStatement. got=%T", + function.Body.Statements[0]) + } + + testInfixExpression(t, bodyStmt.Expression, "x", "+", "y") +} + +func TestFunctionParameterParsing(t *testing.T) { + tests := []struct { + input string + expectedParams []string + }{ + {input: "fn() {};", expectedParams: []string{}}, + {input: "fn(x) {};", expectedParams: []string{"x"}}, + {input: "fn(x, y, z) {};", expectedParams: []string{"x", "y", "z"}}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + function := stmt.Expression.(*ast.FunctionLiteral) + + if len(function.Parameters) != len(tt.expectedParams) { + t.Errorf("length parameters wrong. want %d, got=%d\n", + len(tt.expectedParams), len(function.Parameters)) + } + + for i, ident := range tt.expectedParams { + testLiteralExpression(t, function.Parameters[i], ident) + } + } +} + +func TestCallExpressionParsing(t *testing.T) { + input := "add(1, 2 * 3, 4 + 5);" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("stmt is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.CallExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.CallExpression. got=%T", + stmt.Expression) + } + + if !testIdentifier(t, exp.Function, "add") { + return + } + + if len(exp.Arguments) != 3 { + t.Fatalf("wrong length of arguments. got=%d", len(exp.Arguments)) + } + + testLiteralExpression(t, exp.Arguments[0], 1) + testInfixExpression(t, exp.Arguments[1], 2, "*", 3) + testInfixExpression(t, exp.Arguments[2], 4, "+", 5) +} + +func TestCallExpressionParameterParsing(t *testing.T) { + tests := []struct { + input string + expectedIdent string + expectedArgs []string + }{ + { + input: "add();", + expectedIdent: "add", + expectedArgs: []string{}, + }, + { + input: "add(1);", + expectedIdent: "add", + expectedArgs: []string{"1"}, + }, + { + input: "add(1, 2 * 3, 4 + 5);", + expectedIdent: "add", + expectedArgs: []string{"1", "(2 * 3)", "(4 + 5)"}, + }, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + exp, ok := stmt.Expression.(*ast.CallExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.CallExpression. got=%T", + stmt.Expression) + } + + if !testIdentifier(t, exp.Function, tt.expectedIdent) { + return + } + + if len(exp.Arguments) != len(tt.expectedArgs) { + t.Fatalf("wrong number of arguments. want=%d, got=%d", + len(tt.expectedArgs), len(exp.Arguments)) + } + + for i, arg := range tt.expectedArgs { + if exp.Arguments[i].String() != arg { + t.Errorf("argument %d wrong. want=%q, got=%q", i, + arg, exp.Arguments[i].String()) + } + } + } +} + +func TestStringLiteralExpression(t *testing.T) { + input := `"hello world";` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + literal, ok := stmt.Expression.(*ast.StringLiteral) + if !ok { + t.Fatalf("exp not *ast.StringLiteral. got=%T", stmt.Expression) + } + + if literal.Value != "hello world" { + t.Errorf("literal.Value not %q. got=%q", "hello world", literal.Value) + } +} + +func TestParsingEmptyArrayLiterals(t *testing.T) { + input := "[]" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + array, ok := stmt.Expression.(*ast.ArrayLiteral) + if !ok { + t.Fatalf("exp not ast.ArrayLiteral. got=%T", stmt.Expression) + } + + if len(array.Elements) != 0 { + t.Errorf("len(array.Elements) not 0. got=%d", len(array.Elements)) + } +} + +func TestParsingArrayLiterals(t *testing.T) { + input := "[1, 2 * 2, 3 + 3]" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + array, ok := stmt.Expression.(*ast.ArrayLiteral) + if !ok { + t.Fatalf("exp not ast.ArrayLiteral. got=%T", stmt.Expression) + } + + if len(array.Elements) != 3 { + t.Fatalf("len(array.Elements) not 3. got=%d", len(array.Elements)) + } + + testIntegerLiteral(t, array.Elements[0], 1) + testInfixExpression(t, array.Elements[1], 2, "*", 2) + testInfixExpression(t, array.Elements[2], 3, "+", 3) +} + +func TestParsingIndexExpressions(t *testing.T) { + input := "myArray[1 + 1]" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + indexExp, ok := stmt.Expression.(*ast.IndexExpression) + if !ok { + t.Fatalf("exp not *ast.IndexExpression. got=%T", stmt.Expression) + } + + if !testIdentifier(t, indexExp.Left, "myArray") { + return + } + + if !testInfixExpression(t, indexExp.Index, 1, "+", 1) { + return + } +} + +func TestParsingEmptyHashLiteral(t *testing.T) { + input := "{}" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + if len(hash.Pairs) != 0 { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } +} + +func TestParsingHashLiteralsStringKeys(t *testing.T) { + input := `{"one": 1, "two": 2, "three": 3}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + expected := map[string]int64{ + "one": 1, + "two": 2, + "three": 3, + } + + if len(hash.Pairs) != len(expected) { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } + + for key, value := range hash.Pairs { + literal, ok := key.(*ast.StringLiteral) + if !ok { + t.Errorf("key is not ast.StringLiteral. got=%T", key) + continue + } + + expectedValue := expected[literal.String()] + testIntegerLiteral(t, value, expectedValue) + } +} + +func TestParsingHashLiteralsBooleanKeys(t *testing.T) { + input := `{true: 1, false: 2}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + expected := map[string]int64{ + "true": 1, + "false": 2, + } + + if len(hash.Pairs) != len(expected) { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } + + for key, value := range hash.Pairs { + boolean, ok := key.(*ast.Boolean) + if !ok { + t.Errorf("key is not ast.BooleanLiteral. got=%T", key) + continue + } + + expectedValue := expected[boolean.String()] + testIntegerLiteral(t, value, expectedValue) + } +} + +func TestParsingHashLiteralsIntegerKeys(t *testing.T) { + input := `{1: 1, 2: 2, 3: 3}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + expected := map[string]int64{ + "1": 1, + "2": 2, + "3": 3, + } + + if len(hash.Pairs) != len(expected) { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } + + for key, value := range hash.Pairs { + integer, ok := key.(*ast.IntegerLiteral) + if !ok { + t.Errorf("key is not ast.IntegerLiteral. got=%T", key) + continue + } + + expectedValue := expected[integer.String()] + + testIntegerLiteral(t, value, expectedValue) + } +} + +func TestParsingHashLiteralsWithExpressions(t *testing.T) { + input := `{"one": 0 + 1, "two": 10 - 8, "three": 15 / 5}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + if len(hash.Pairs) != 3 { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } + + tests := map[string]func(ast.Expression){ + "one": func(e ast.Expression) { + testInfixExpression(t, e, 0, "+", 1) + }, + "two": func(e ast.Expression) { + testInfixExpression(t, e, 10, "-", 8) + }, + "three": func(e ast.Expression) { + testInfixExpression(t, e, 15, "/", 5) + }, + } + + for key, value := range hash.Pairs { + literal, ok := key.(*ast.StringLiteral) + if !ok { + t.Errorf("key is not ast.StringLiteral. got=%T", key) + continue + } + + testFunc, ok := tests[literal.String()] + if !ok { + t.Errorf("No test function for key %q found", literal.String()) + continue + } + + testFunc(value) + } +} + +func testLetStatement(t *testing.T, s ast.Statement, name string) bool { + if s.TokenLiteral() != "let" { + t.Errorf("s.TokenLiteral not 'let'. got=%q", s.TokenLiteral()) + return false + } + + letStmt, ok := s.(*ast.LetStatement) + if !ok { + t.Errorf("s not *ast.LetStatement. got=%T", s) + return false + } + + if letStmt.Name.Value != name { + t.Errorf("letStmt.Name.Value not '%s'. got=%s", name, letStmt.Name.Value) + return false + } + + if letStmt.Name.TokenLiteral() != name { + t.Errorf("letStmt.Name.TokenLiteral() not '%s'. got=%s", + name, letStmt.Name.TokenLiteral()) + return false + } + + return true +} + +func testInfixExpression(t *testing.T, exp ast.Expression, left interface{}, + operator string, right interface{}) bool { + + opExp, ok := exp.(*ast.InfixExpression) + if !ok { + t.Errorf("exp is not ast.InfixExpression. got=%T(%s)", exp, exp) + return false + } + + if !testLiteralExpression(t, opExp.Left, left) { + return false + } + + if opExp.Operator != operator { + t.Errorf("exp.Operator is not '%s'. got=%q", operator, opExp.Operator) + return false + } + + if !testLiteralExpression(t, opExp.Right, right) { + return false + } + + return true +} + +func testLiteralExpression( + t *testing.T, + exp ast.Expression, + expected interface{}, +) bool { + switch v := expected.(type) { + case int: + return testIntegerLiteral(t, exp, int64(v)) + case int64: + return testIntegerLiteral(t, exp, v) + case string: + return testIdentifier(t, exp, v) + case bool: + return testBooleanLiteral(t, exp, v) + } + t.Errorf("type of exp not handled. got=%T", exp) + return false +} + +func testIntegerLiteral(t *testing.T, il ast.Expression, value int64) bool { + integ, ok := il.(*ast.IntegerLiteral) + if !ok { + t.Errorf("il not *ast.IntegerLiteral. got=%T", il) + return false + } + + if integ.Value != value { + t.Errorf("integ.Value not %d. got=%d", value, integ.Value) + return false + } + + if integ.TokenLiteral() != fmt.Sprintf("%d", value) { + t.Errorf("integ.TokenLiteral not %d. got=%s", value, + integ.TokenLiteral()) + return false + } + + return true +} + +func testIdentifier(t *testing.T, exp ast.Expression, value string) bool { + ident, ok := exp.(*ast.Identifier) + if !ok { + t.Errorf("exp not *ast.Identifier. got=%T", exp) + return false + } + + if ident.Value != value { + t.Errorf("ident.Value not %s. got=%s", value, ident.Value) + return false + } + + if ident.TokenLiteral() != value { + t.Errorf("ident.TokenLiteral not %s. got=%s", value, + ident.TokenLiteral()) + return false + } + + return true +} + +func testBooleanLiteral(t *testing.T, exp ast.Expression, value bool) bool { + bo, ok := exp.(*ast.Boolean) + if !ok { + t.Errorf("exp not *ast.Boolean. got=%T", exp) + return false + } + + if bo.Value != value { + t.Errorf("bo.Value not %t. got=%t", value, bo.Value) + return false + } + + if bo.TokenLiteral() != fmt.Sprintf("%t", value) { + t.Errorf("bo.TokenLiteral not %t. got=%s", + value, bo.TokenLiteral()) + return false + } + + return true +} + +func checkParserErrors(t *testing.T, p *Parser) { + errors := p.Errors() + if len(errors) == 0 { + return + } + + t.Errorf("parser has %d errors", len(errors)) + for _, msg := range errors { + t.Errorf("parser error: %q", msg) + } + t.FailNow() +} diff --git a/wcig_code_1_2/04/src/monkey/parser/parser_tracing.go b/wcig_code_1_2/04/src/monkey/parser/parser_tracing.go new file mode 100644 index 0000000..5fc569b --- /dev/null +++ b/wcig_code_1_2/04/src/monkey/parser/parser_tracing.go @@ -0,0 +1,32 @@ +package parser + +import ( + "fmt" + "strings" +) + +var traceLevel int = 0 + +const traceIdentPlaceholder string = "\t" + +func identLevel() string { + return strings.Repeat(traceIdentPlaceholder, traceLevel-1) +} + +func tracePrint(fs string) { + fmt.Printf("%s%s\n", identLevel(), fs) +} + +func incIdent() { traceLevel = traceLevel + 1 } +func decIdent() { traceLevel = traceLevel - 1 } + +func trace(msg string) string { + incIdent() + tracePrint("BEGIN " + msg) + return msg +} + +func untrace(msg string) { + tracePrint("END " + msg) + decIdent() +} diff --git a/wcig_code_1_2/04/src/monkey/repl/repl.go b/wcig_code_1_2/04/src/monkey/repl/repl.go new file mode 100644 index 0000000..b5fbe0e --- /dev/null +++ b/wcig_code_1_2/04/src/monkey/repl/repl.go @@ -0,0 +1,75 @@ +package repl + +import ( + "bufio" + "fmt" + "io" + "monkey/compiler" + "monkey/lexer" + "monkey/parser" + "monkey/vm" +) + +const PROMPT = ">> " + +func Start(in io.Reader, out io.Writer) { + scanner := bufio.NewScanner(in) + + for { + fmt.Fprintf(out, PROMPT) + scanned := scanner.Scan() + if !scanned { + return + } + + line := scanner.Text() + l := lexer.New(line) + p := parser.New(l) + + program := p.ParseProgram() + if len(p.Errors()) != 0 { + printParserErrors(out, p.Errors()) + continue + } + + comp := compiler.New() + err := comp.Compile(program) + if err != nil { + fmt.Fprintf(out, "Woops! Compilation failed:\n %s\n", err) + continue + } + + machine := vm.New(comp.Bytecode()) + err = machine.Run() + if err != nil { + fmt.Fprintf(out, "Woops! Executing bytecode failed:\n %s\n", err) + continue + } + + lastPopped := machine.LastPoppedStackElem() + io.WriteString(out, lastPopped.Inspect()) + io.WriteString(out, "\n") + } +} + +const MONKEY_FACE = ` __,__ + .--. .-" "-. .--. + / .. \/ .-. .-. \/ .. \ + | | '| / Y \ |' | | + | \ \ \ 0 | 0 / / / | + \ '- ,\.-"""""""-./, -' / + ''-' /_ ^ ^ _\ '-'' + | \._ _./ | + \ \ '~' / / + '._ '-=-' _.' + '-----' +` + +func printParserErrors(out io.Writer, errors []string) { + io.WriteString(out, MONKEY_FACE) + io.WriteString(out, "Woops! We ran into some monkey business here!\n") + io.WriteString(out, " parser errors:\n") + for _, msg := range errors { + io.WriteString(out, "\t"+msg+"\n") + } +} diff --git a/wcig_code_1_2/04/src/monkey/token/token.go b/wcig_code_1_2/04/src/monkey/token/token.go new file mode 100644 index 0000000..3d2d2f7 --- /dev/null +++ b/wcig_code_1_2/04/src/monkey/token/token.go @@ -0,0 +1,70 @@ +package token + +type TokenType string + +const ( + ILLEGAL = "ILLEGAL" + EOF = "EOF" + + // Identifiers + literals + IDENT = "IDENT" // add, foobar, x, y, ... + INT = "INT" // 1343456 + STRING = "STRING" // "foobar" + + // Operators + ASSIGN = "=" + PLUS = "+" + MINUS = "-" + BANG = "!" + ASTERISK = "*" + SLASH = "/" + + LT = "<" + GT = ">" + + EQ = "==" + NOT_EQ = "!=" + + // Delimiters + COMMA = "," + SEMICOLON = ";" + COLON = ":" + + LPAREN = "(" + RPAREN = ")" + LBRACE = "{" + RBRACE = "}" + LBRACKET = "[" + RBRACKET = "]" + + // Keywords + FUNCTION = "FUNCTION" + LET = "LET" + TRUE = "TRUE" + FALSE = "FALSE" + IF = "IF" + ELSE = "ELSE" + RETURN = "RETURN" +) + +type Token struct { + Type TokenType + Literal string +} + +var keywords = map[string]TokenType{ + "fn": FUNCTION, + "let": LET, + "true": TRUE, + "false": FALSE, + "if": IF, + "else": ELSE, + "return": RETURN, +} + +func LookupIdent(ident string) TokenType { + if tok, ok := keywords[ident]; ok { + return tok + } + return IDENT +} diff --git a/wcig_code_1_2/04/src/monkey/vm/vm.go b/wcig_code_1_2/04/src/monkey/vm/vm.go new file mode 100644 index 0000000..ab35fbf --- /dev/null +++ b/wcig_code_1_2/04/src/monkey/vm/vm.go @@ -0,0 +1,256 @@ +package vm + +import ( + "fmt" + "monkey/code" + "monkey/compiler" + "monkey/object" +) + +const StackSize = 2048 + +var True = &object.Boolean{Value: true} +var False = &object.Boolean{Value: false} +var Null = &object.Null{} + +type VM struct { + constants []object.Object + instructions code.Instructions + + stack []object.Object + sp int // Always points to the next value. Top of stack is stack[sp-1] +} + +func New(bytecode *compiler.Bytecode) *VM { + return &VM{ + instructions: bytecode.Instructions, + constants: bytecode.Constants, + + stack: make([]object.Object, StackSize), + sp: 0, + } +} + +func (vm *VM) LastPoppedStackElem() object.Object { + return vm.stack[vm.sp] +} + +func (vm *VM) Run() error { + for ip := 0; ip < len(vm.instructions); ip++ { + op := code.Opcode(vm.instructions[ip]) + + switch op { + case code.OpConstant: + constIndex := code.ReadUint16(vm.instructions[ip+1:]) + ip += 2 + + err := vm.push(vm.constants[constIndex]) + if err != nil { + return err + } + + case code.OpPop: + vm.pop() + + case code.OpAdd, code.OpSub, code.OpMul, code.OpDiv: + err := vm.executeBinaryOperation(op) + if err != nil { + return err + } + + case code.OpTrue: + err := vm.push(True) + if err != nil { + return err + } + + case code.OpFalse: + err := vm.push(False) + if err != nil { + return err + } + + case code.OpEqual, code.OpNotEqual, code.OpGreaterThan: + err := vm.executeComparison(op) + if err != nil { + return err + } + + case code.OpBang: + err := vm.executeBangOperator() + if err != nil { + return err + } + + case code.OpMinus: + err := vm.executeMinusOperator() + if err != nil { + return err + } + + case code.OpJump: + pos := int(code.ReadUint16(vm.instructions[ip+1:])) + ip = pos - 1 + + case code.OpJumpNotTruthy: + pos := int(code.ReadUint16(vm.instructions[ip+1:])) + ip += 2 + + condition := vm.pop() + if !isTruthy(condition) { + ip = pos - 1 + } + + case code.OpNull: + err := vm.push(Null) + if err != nil { + return err + } + + } + } + + return nil +} + +func (vm *VM) push(o object.Object) error { + if vm.sp >= StackSize { + return fmt.Errorf("stack overflow") + } + + vm.stack[vm.sp] = o + vm.sp++ + + return nil +} + +func (vm *VM) pop() object.Object { + o := vm.stack[vm.sp-1] + vm.sp-- + return o +} + +func (vm *VM) executeBinaryOperation(op code.Opcode) error { + right := vm.pop() + left := vm.pop() + + leftType := left.Type() + rightType := right.Type() + + if leftType == object.INTEGER_OBJ && rightType == object.INTEGER_OBJ { + return vm.executeBinaryIntegerOperation(op, left, right) + } + + return fmt.Errorf("unsupported types for binary operation: %s %s", + leftType, rightType) +} + +func (vm *VM) executeBinaryIntegerOperation( + op code.Opcode, + left, right object.Object, +) error { + leftValue := left.(*object.Integer).Value + rightValue := right.(*object.Integer).Value + + var result int64 + + switch op { + case code.OpAdd: + result = leftValue + rightValue + case code.OpSub: + result = leftValue - rightValue + case code.OpMul: + result = leftValue * rightValue + case code.OpDiv: + result = leftValue / rightValue + default: + return fmt.Errorf("unknown integer operator: %d", op) + } + + return vm.push(&object.Integer{Value: result}) +} + +func (vm *VM) executeComparison(op code.Opcode) error { + right := vm.pop() + left := vm.pop() + + if left.Type() == object.INTEGER_OBJ && right.Type() == object.INTEGER_OBJ { + return vm.executeIntegerComparison(op, left, right) + } + + switch op { + case code.OpEqual: + return vm.push(nativeBoolToBooleanObject(right == left)) + case code.OpNotEqual: + return vm.push(nativeBoolToBooleanObject(right != left)) + default: + return fmt.Errorf("unknown operator: %d (%s %s)", + op, left.Type(), right.Type()) + } +} + +func (vm *VM) executeIntegerComparison( + op code.Opcode, + left, right object.Object, +) error { + leftValue := left.(*object.Integer).Value + rightValue := right.(*object.Integer).Value + + switch op { + case code.OpEqual: + return vm.push(nativeBoolToBooleanObject(rightValue == leftValue)) + case code.OpNotEqual: + return vm.push(nativeBoolToBooleanObject(rightValue != leftValue)) + case code.OpGreaterThan: + return vm.push(nativeBoolToBooleanObject(leftValue > rightValue)) + default: + return fmt.Errorf("unknown operator: %d", op) + } +} + +func (vm *VM) executeBangOperator() error { + operand := vm.pop() + + switch operand { + case True: + return vm.push(False) + case False: + return vm.push(True) + case Null: + return vm.push(True) + default: + return vm.push(False) + } +} + +func (vm *VM) executeMinusOperator() error { + operand := vm.pop() + + if operand.Type() != object.INTEGER_OBJ { + return fmt.Errorf("unsupported type for negation: %s", operand.Type()) + } + + value := operand.(*object.Integer).Value + return vm.push(&object.Integer{Value: -value}) +} + +func nativeBoolToBooleanObject(input bool) *object.Boolean { + if input { + return True + } + return False +} + +func isTruthy(obj object.Object) bool { + switch obj := obj.(type) { + + case *object.Boolean: + return obj.Value + + case *object.Null: + return false + + default: + return true + } +} diff --git a/wcig_code_1_2/04/src/monkey/vm/vm_test.go b/wcig_code_1_2/04/src/monkey/vm/vm_test.go new file mode 100644 index 0000000..b9c649e --- /dev/null +++ b/wcig_code_1_2/04/src/monkey/vm/vm_test.go @@ -0,0 +1,177 @@ +package vm + +import ( + "fmt" + "monkey/ast" + "monkey/compiler" + "monkey/lexer" + "monkey/object" + "monkey/parser" + "testing" +) + +func TestIntegerArithmetic(t *testing.T) { + tests := []vmTestCase{ + {"1", 1}, + {"2", 2}, + {"1 + 2", 3}, + {"1 - 2", -1}, + {"1 * 2", 2}, + {"4 / 2", 2}, + {"50 / 2 * 2 + 10 - 5", 55}, + {"5 * (2 + 10)", 60}, + {"5 + 5 + 5 + 5 - 10", 10}, + {"2 * 2 * 2 * 2 * 2", 32}, + {"5 * 2 + 10", 20}, + {"5 + 2 * 10", 25}, + {"5 * (2 + 10)", 60}, + {"-5", -5}, + {"-10", -10}, + {"-50 + 100 + -50", 0}, + {"(5 + 10 * 2 + 15 / 3) * 2 + -10", 50}, + } + + runVmTests(t, tests) +} + +func TestBooleanExpressions(t *testing.T) { + tests := []vmTestCase{ + {"true", true}, + {"false", false}, + {"1 < 2", true}, + {"1 > 2", false}, + {"1 < 1", false}, + {"1 > 1", false}, + {"1 == 1", true}, + {"1 != 1", false}, + {"1 == 2", false}, + {"1 != 2", true}, + {"true == true", true}, + {"false == false", true}, + {"true == false", false}, + {"true != false", true}, + {"false != true", true}, + {"(1 < 2) == true", true}, + {"(1 < 2) == false", false}, + {"(1 > 2) == true", false}, + {"(1 > 2) == false", true}, + {"!true", false}, + {"!false", true}, + {"!5", false}, + {"!!true", true}, + {"!!false", false}, + {"!!5", true}, + {"!(if (false) { 5; })", true}, + } + + runVmTests(t, tests) +} + +func TestConditionals(t *testing.T) { + tests := []vmTestCase{ + {"if (true) { 10 }", 10}, + {"if (true) { 10 } else { 20 }", 10}, + {"if (false) { 10 } else { 20 } ", 20}, + {"if (1) { 10 }", 10}, + {"if (1 < 2) { 10 }", 10}, + {"if (1 < 2) { 10 } else { 20 }", 10}, + {"if (1 > 2) { 10 } else { 20 }", 20}, + {"if (1 > 2) { 10 }", Null}, + {"if (false) { 10 }", Null}, + {"if ((if (false) { 10 })) { 10 } else { 20 }", 20}, + } + + runVmTests(t, tests) +} + +type vmTestCase struct { + input string + expected interface{} +} + +func runVmTests(t *testing.T, tests []vmTestCase) { + t.Helper() + + for _, tt := range tests { + program := parse(tt.input) + + comp := compiler.New() + err := comp.Compile(program) + if err != nil { + t.Fatalf("compiler error: %s", err) + } + + vm := New(comp.Bytecode()) + err = vm.Run() + if err != nil { + t.Fatalf("vm error: %s", err) + } + + stackElem := vm.LastPoppedStackElem() + + testExpectedObject(t, tt.expected, stackElem) + } +} + +func parse(input string) *ast.Program { + l := lexer.New(input) + p := parser.New(l) + return p.ParseProgram() +} + +func testExpectedObject( + t *testing.T, + expected interface{}, + actual object.Object, +) { + t.Helper() + + switch expected := expected.(type) { + case int: + err := testIntegerObject(int64(expected), actual) + if err != nil { + t.Errorf("testIntegerObject failed: %s", err) + } + + case bool: + err := testBooleanObject(bool(expected), actual) + if err != nil { + t.Errorf("testBooleanObject failed: %s", err) + } + + case *object.Null: + if actual != Null { + t.Errorf("object is not Null: %T (%+v)", actual, actual) + } + } +} + +func testIntegerObject(expected int64, actual object.Object) error { + result, ok := actual.(*object.Integer) + if !ok { + return fmt.Errorf("object is not Integer. got=%T (%+v)", + actual, actual) + } + + if result.Value != expected { + return fmt.Errorf("object has wrong value. got=%d, want=%d", + result.Value, expected) + } + + return nil +} + +func testBooleanObject(expected bool, actual object.Object) error { + result, ok := actual.(*object.Boolean) + if !ok { + return fmt.Errorf("object is not Boolean. got=%T (%+v)", + actual, actual) + } + + if result.Value != expected { + return fmt.Errorf("object has wrong value. got=%t, want=%t", + result.Value, expected) + } + + return nil +} diff --git a/wcig_code_1_2/05/.envrc b/wcig_code_1_2/05/.envrc new file mode 100644 index 0000000..fb65ef4 --- /dev/null +++ b/wcig_code_1_2/05/.envrc @@ -0,0 +1 @@ +export GOPATH=$(pwd) diff --git a/wcig_code_1_2/05/src/monkey/ast/ast.go b/wcig_code_1_2/05/src/monkey/ast/ast.go new file mode 100644 index 0000000..fb30b05 --- /dev/null +++ b/wcig_code_1_2/05/src/monkey/ast/ast.go @@ -0,0 +1,339 @@ +package ast + +import ( + "bytes" + "monkey/token" + "strings" +) + +// The base Node interface +type Node interface { + TokenLiteral() string + String() string +} + +// All statement nodes implement this +type Statement interface { + Node + statementNode() +} + +// All expression nodes implement this +type Expression interface { + Node + expressionNode() +} + +type Program struct { + Statements []Statement +} + +func (p *Program) TokenLiteral() string { + if len(p.Statements) > 0 { + return p.Statements[0].TokenLiteral() + } else { + return "" + } +} + +func (p *Program) String() string { + var out bytes.Buffer + + for _, s := range p.Statements { + out.WriteString(s.String()) + } + + return out.String() +} + +// Statements +type LetStatement struct { + Token token.Token // the token.LET token + Name *Identifier + Value Expression +} + +func (ls *LetStatement) statementNode() {} +func (ls *LetStatement) TokenLiteral() string { return ls.Token.Literal } +func (ls *LetStatement) String() string { + var out bytes.Buffer + + out.WriteString(ls.TokenLiteral() + " ") + out.WriteString(ls.Name.String()) + out.WriteString(" = ") + + if ls.Value != nil { + out.WriteString(ls.Value.String()) + } + + out.WriteString(";") + + return out.String() +} + +type ReturnStatement struct { + Token token.Token // the 'return' token + ReturnValue Expression +} + +func (rs *ReturnStatement) statementNode() {} +func (rs *ReturnStatement) TokenLiteral() string { return rs.Token.Literal } +func (rs *ReturnStatement) String() string { + var out bytes.Buffer + + out.WriteString(rs.TokenLiteral() + " ") + + if rs.ReturnValue != nil { + out.WriteString(rs.ReturnValue.String()) + } + + out.WriteString(";") + + return out.String() +} + +type ExpressionStatement struct { + Token token.Token // the first token of the expression + Expression Expression +} + +func (es *ExpressionStatement) statementNode() {} +func (es *ExpressionStatement) TokenLiteral() string { return es.Token.Literal } +func (es *ExpressionStatement) String() string { + if es.Expression != nil { + return es.Expression.String() + } + return "" +} + +type BlockStatement struct { + Token token.Token // the { token + Statements []Statement +} + +func (bs *BlockStatement) statementNode() {} +func (bs *BlockStatement) TokenLiteral() string { return bs.Token.Literal } +func (bs *BlockStatement) String() string { + var out bytes.Buffer + + for _, s := range bs.Statements { + out.WriteString(s.String()) + } + + return out.String() +} + +// Expressions +type Identifier struct { + Token token.Token // the token.IDENT token + Value string +} + +func (i *Identifier) expressionNode() {} +func (i *Identifier) TokenLiteral() string { return i.Token.Literal } +func (i *Identifier) String() string { return i.Value } + +type Boolean struct { + Token token.Token + Value bool +} + +func (b *Boolean) expressionNode() {} +func (b *Boolean) TokenLiteral() string { return b.Token.Literal } +func (b *Boolean) String() string { return b.Token.Literal } + +type IntegerLiteral struct { + Token token.Token + Value int64 +} + +func (il *IntegerLiteral) expressionNode() {} +func (il *IntegerLiteral) TokenLiteral() string { return il.Token.Literal } +func (il *IntegerLiteral) String() string { return il.Token.Literal } + +type PrefixExpression struct { + Token token.Token // The prefix token, e.g. ! + Operator string + Right Expression +} + +func (pe *PrefixExpression) expressionNode() {} +func (pe *PrefixExpression) TokenLiteral() string { return pe.Token.Literal } +func (pe *PrefixExpression) String() string { + var out bytes.Buffer + + out.WriteString("(") + out.WriteString(pe.Operator) + out.WriteString(pe.Right.String()) + out.WriteString(")") + + return out.String() +} + +type InfixExpression struct { + Token token.Token // The operator token, e.g. + + Left Expression + Operator string + Right Expression +} + +func (oe *InfixExpression) expressionNode() {} +func (oe *InfixExpression) TokenLiteral() string { return oe.Token.Literal } +func (oe *InfixExpression) String() string { + var out bytes.Buffer + + out.WriteString("(") + out.WriteString(oe.Left.String()) + out.WriteString(" " + oe.Operator + " ") + out.WriteString(oe.Right.String()) + out.WriteString(")") + + return out.String() +} + +type IfExpression struct { + Token token.Token // The 'if' token + Condition Expression + Consequence *BlockStatement + Alternative *BlockStatement +} + +func (ie *IfExpression) expressionNode() {} +func (ie *IfExpression) TokenLiteral() string { return ie.Token.Literal } +func (ie *IfExpression) String() string { + var out bytes.Buffer + + out.WriteString("if") + out.WriteString(ie.Condition.String()) + out.WriteString(" ") + out.WriteString(ie.Consequence.String()) + + if ie.Alternative != nil { + out.WriteString("else ") + out.WriteString(ie.Alternative.String()) + } + + return out.String() +} + +type FunctionLiteral struct { + Token token.Token // The 'fn' token + Parameters []*Identifier + Body *BlockStatement +} + +func (fl *FunctionLiteral) expressionNode() {} +func (fl *FunctionLiteral) TokenLiteral() string { return fl.Token.Literal } +func (fl *FunctionLiteral) String() string { + var out bytes.Buffer + + params := []string{} + for _, p := range fl.Parameters { + params = append(params, p.String()) + } + + out.WriteString(fl.TokenLiteral()) + out.WriteString("(") + out.WriteString(strings.Join(params, ", ")) + out.WriteString(") ") + out.WriteString(fl.Body.String()) + + return out.String() +} + +type CallExpression struct { + Token token.Token // The '(' token + Function Expression // Identifier or FunctionLiteral + Arguments []Expression +} + +func (ce *CallExpression) expressionNode() {} +func (ce *CallExpression) TokenLiteral() string { return ce.Token.Literal } +func (ce *CallExpression) String() string { + var out bytes.Buffer + + args := []string{} + for _, a := range ce.Arguments { + args = append(args, a.String()) + } + + out.WriteString(ce.Function.String()) + out.WriteString("(") + out.WriteString(strings.Join(args, ", ")) + out.WriteString(")") + + return out.String() +} + +type StringLiteral struct { + Token token.Token + Value string +} + +func (sl *StringLiteral) expressionNode() {} +func (sl *StringLiteral) TokenLiteral() string { return sl.Token.Literal } +func (sl *StringLiteral) String() string { return sl.Token.Literal } + +type ArrayLiteral struct { + Token token.Token // the '[' token + Elements []Expression +} + +func (al *ArrayLiteral) expressionNode() {} +func (al *ArrayLiteral) TokenLiteral() string { return al.Token.Literal } +func (al *ArrayLiteral) String() string { + var out bytes.Buffer + + elements := []string{} + for _, el := range al.Elements { + elements = append(elements, el.String()) + } + + out.WriteString("[") + out.WriteString(strings.Join(elements, ", ")) + out.WriteString("]") + + return out.String() +} + +type IndexExpression struct { + Token token.Token // The [ token + Left Expression + Index Expression +} + +func (ie *IndexExpression) expressionNode() {} +func (ie *IndexExpression) TokenLiteral() string { return ie.Token.Literal } +func (ie *IndexExpression) String() string { + var out bytes.Buffer + + out.WriteString("(") + out.WriteString(ie.Left.String()) + out.WriteString("[") + out.WriteString(ie.Index.String()) + out.WriteString("])") + + return out.String() +} + +type HashLiteral struct { + Token token.Token // the '{' token + Pairs map[Expression]Expression +} + +func (hl *HashLiteral) expressionNode() {} +func (hl *HashLiteral) TokenLiteral() string { return hl.Token.Literal } +func (hl *HashLiteral) String() string { + var out bytes.Buffer + + pairs := []string{} + for key, value := range hl.Pairs { + pairs = append(pairs, key.String()+":"+value.String()) + } + + out.WriteString("{") + out.WriteString(strings.Join(pairs, ", ")) + out.WriteString("}") + + return out.String() +} diff --git a/wcig_code_1_2/05/src/monkey/ast/ast_test.go b/wcig_code_1_2/05/src/monkey/ast/ast_test.go new file mode 100644 index 0000000..14a49dc --- /dev/null +++ b/wcig_code_1_2/05/src/monkey/ast/ast_test.go @@ -0,0 +1,28 @@ +package ast + +import ( + "monkey/token" + "testing" +) + +func TestString(t *testing.T) { + program := &Program{ + Statements: []Statement{ + &LetStatement{ + Token: token.Token{Type: token.LET, Literal: "let"}, + Name: &Identifier{ + Token: token.Token{Type: token.IDENT, Literal: "myVar"}, + Value: "myVar", + }, + Value: &Identifier{ + Token: token.Token{Type: token.IDENT, Literal: "anotherVar"}, + Value: "anotherVar", + }, + }, + }, + } + + if program.String() != "let myVar = anotherVar;" { + t.Errorf("program.String() wrong. got=%q", program.String()) + } +} diff --git a/wcig_code_1_2/05/src/monkey/code/code.go b/wcig_code_1_2/05/src/monkey/code/code.go new file mode 100644 index 0000000..66bc2cf --- /dev/null +++ b/wcig_code_1_2/05/src/monkey/code/code.go @@ -0,0 +1,171 @@ +package code + +import ( + "bytes" + "encoding/binary" + "fmt" +) + +type Instructions []byte + +func (ins Instructions) String() string { + var out bytes.Buffer + + i := 0 + for i < len(ins) { + def, err := Lookup(ins[i]) + if err != nil { + fmt.Fprintf(&out, "ERROR: %s\n", err) + continue + } + + operands, read := ReadOperands(def, ins[i+1:]) + + fmt.Fprintf(&out, "%04d %s\n", i, ins.fmtInstruction(def, operands)) + + i += 1 + read + } + + return out.String() +} + +func (ins Instructions) fmtInstruction(def *Definition, operands []int) string { + operandCount := len(def.OperandWidths) + + if len(operands) != operandCount { + return fmt.Sprintf("ERROR: operand len %d does not match defined %d\n", + len(operands), operandCount) + } + + switch operandCount { + case 0: + return def.Name + case 1: + return fmt.Sprintf("%s %d", def.Name, operands[0]) + } + + return fmt.Sprintf("ERROR: unhandled operandCount for %s\n", def.Name) +} + +type Opcode byte + +const ( + OpConstant Opcode = iota + + OpAdd + + OpPop + + OpSub + OpMul + OpDiv + + OpTrue + OpFalse + + OpEqual + OpNotEqual + OpGreaterThan + + OpMinus + OpBang + + OpJumpNotTruthy + OpJump + + OpNull + + OpGetGlobal + OpSetGlobal +) + +type Definition struct { + Name string + OperandWidths []int +} + +var definitions = map[Opcode]*Definition{ + OpConstant: {"OpConstant", []int{2}}, + + OpAdd: {"OpAdd", []int{}}, + + OpPop: {"OpPop", []int{}}, + + OpSub: {"OpSub", []int{}}, + OpMul: {"OpMul", []int{}}, + OpDiv: {"OpDiv", []int{}}, + + OpTrue: {"OpTrue", []int{}}, + OpFalse: {"OpFalse", []int{}}, + + OpEqual: {"OpEqual", []int{}}, + OpNotEqual: {"OpNotEqual", []int{}}, + OpGreaterThan: {"OpGreaterThan", []int{}}, + + OpMinus: {"OpMinus", []int{}}, + OpBang: {"OpBang", []int{}}, + + OpJumpNotTruthy: {"OpJumpNotTruthy", []int{2}}, + OpJump: {"OpJump", []int{2}}, + + OpNull: {"OpNull", []int{}}, + + OpGetGlobal: {"OpGetGlobal", []int{2}}, + OpSetGlobal: {"OpSetGlobal", []int{2}}, +} + +func Lookup(op byte) (*Definition, error) { + def, ok := definitions[Opcode(op)] + if !ok { + return nil, fmt.Errorf("opcode %d undefined", op) + } + + return def, nil +} + +func Make(op Opcode, operands ...int) []byte { + def, ok := definitions[op] + if !ok { + return []byte{} + } + + instructionLen := 1 + for _, w := range def.OperandWidths { + instructionLen += w + } + + instruction := make([]byte, instructionLen) + instruction[0] = byte(op) + + offset := 1 + for i, o := range operands { + width := def.OperandWidths[i] + switch width { + case 2: + binary.BigEndian.PutUint16(instruction[offset:], uint16(o)) + } + offset += width + } + + return instruction +} + +func ReadOperands(def *Definition, ins Instructions) ([]int, int) { + operands := make([]int, len(def.OperandWidths)) + offset := 0 + + for i, width := range def.OperandWidths { + switch width { + case 2: + operands[i] = int(ReadUint16(ins[offset:])) + } + + offset += width + } + + return operands, offset +} + +func ReadUint16(ins Instructions) uint16 { + return binary.BigEndian.Uint16(ins) +} diff --git a/wcig_code_1_2/05/src/monkey/code/code_test.go b/wcig_code_1_2/05/src/monkey/code/code_test.go new file mode 100644 index 0000000..a353025 --- /dev/null +++ b/wcig_code_1_2/05/src/monkey/code/code_test.go @@ -0,0 +1,83 @@ +package code + +import "testing" + +func TestMake(t *testing.T) { + tests := []struct { + op Opcode + operands []int + expected []byte + }{ + {OpConstant, []int{65534}, []byte{byte(OpConstant), 255, 254}}, + {OpAdd, []int{}, []byte{byte(OpAdd)}}, + } + + for _, tt := range tests { + instruction := Make(tt.op, tt.operands...) + + if len(instruction) != len(tt.expected) { + t.Errorf("instruction has wrong length. want=%d, got=%d", + len(tt.expected), len(instruction)) + } + + for i, b := range tt.expected { + if instruction[i] != tt.expected[i] { + t.Errorf("wrong byte at pos %d. want=%d, got=%d", + i, b, instruction[i]) + } + } + } +} + +func TestInstructionsString(t *testing.T) { + instructions := []Instructions{ + Make(OpAdd), + Make(OpConstant, 2), + Make(OpConstant, 65535), + } + + expected := `0000 OpAdd +0001 OpConstant 2 +0004 OpConstant 65535 +` + + concatted := Instructions{} + for _, ins := range instructions { + concatted = append(concatted, ins...) + } + + if concatted.String() != expected { + t.Errorf("instructions wrongly formatted.\nwant=%q\ngot=%q", + expected, concatted.String()) + } +} + +func TestReadOperands(t *testing.T) { + tests := []struct { + op Opcode + operands []int + bytesRead int + }{ + {OpConstant, []int{65535}, 2}, + } + + for _, tt := range tests { + instruction := Make(tt.op, tt.operands...) + + def, err := Lookup(byte(tt.op)) + if err != nil { + t.Fatalf("definition not found: %q\n", err) + } + + operandsRead, n := ReadOperands(def, instruction[1:]) + if n != tt.bytesRead { + t.Fatalf("n wrong. want=%d, got=%d", tt.bytesRead, n) + } + + for i, want := range tt.operands { + if operandsRead[i] != want { + t.Errorf("operand wrong. want=%d, got=%d", want, operandsRead[i]) + } + } + } +} diff --git a/wcig_code_1_2/05/src/monkey/compiler/compiler.go b/wcig_code_1_2/05/src/monkey/compiler/compiler.go new file mode 100644 index 0000000..13b5a69 --- /dev/null +++ b/wcig_code_1_2/05/src/monkey/compiler/compiler.go @@ -0,0 +1,257 @@ +package compiler + +import ( + "fmt" + "monkey/ast" + "monkey/code" + "monkey/object" +) + +type Compiler struct { + instructions code.Instructions + constants []object.Object + + lastInstruction EmittedInstruction + previousInstruction EmittedInstruction + + symbolTable *SymbolTable +} + +func New() *Compiler { + return &Compiler{ + instructions: code.Instructions{}, + constants: []object.Object{}, + lastInstruction: EmittedInstruction{}, + previousInstruction: EmittedInstruction{}, + symbolTable: NewSymbolTable(), + } +} + +func NewWithState(s *SymbolTable, constants []object.Object) *Compiler { + compiler := New() + compiler.symbolTable = s + compiler.constants = constants + return compiler +} + +func (c *Compiler) Compile(node ast.Node) error { + switch node := node.(type) { + case *ast.Program: + for _, s := range node.Statements { + err := c.Compile(s) + if err != nil { + return err + } + } + + case *ast.ExpressionStatement: + err := c.Compile(node.Expression) + if err != nil { + return err + } + c.emit(code.OpPop) + + case *ast.InfixExpression: + if node.Operator == "<" { + err := c.Compile(node.Right) + if err != nil { + return err + } + + err = c.Compile(node.Left) + if err != nil { + return err + } + c.emit(code.OpGreaterThan) + return nil + } + + err := c.Compile(node.Left) + if err != nil { + return err + } + + err = c.Compile(node.Right) + if err != nil { + return err + } + + switch node.Operator { + case "+": + c.emit(code.OpAdd) + case "-": + c.emit(code.OpSub) + case "*": + c.emit(code.OpMul) + case "/": + c.emit(code.OpDiv) + case ">": + c.emit(code.OpGreaterThan) + case "==": + c.emit(code.OpEqual) + case "!=": + c.emit(code.OpNotEqual) + default: + return fmt.Errorf("unknown operator %s", node.Operator) + } + + case *ast.IntegerLiteral: + integer := &object.Integer{Value: node.Value} + c.emit(code.OpConstant, c.addConstant(integer)) + + case *ast.Boolean: + if node.Value { + c.emit(code.OpTrue) + } else { + c.emit(code.OpFalse) + } + + case *ast.PrefixExpression: + err := c.Compile(node.Right) + if err != nil { + return err + } + + switch node.Operator { + case "!": + c.emit(code.OpBang) + case "-": + c.emit(code.OpMinus) + default: + return fmt.Errorf("unknown operator %s", node.Operator) + } + + case *ast.IfExpression: + err := c.Compile(node.Condition) + if err != nil { + return err + } + + // Emit an `OpJumpNotTruthy` with a bogus value + jumpNotTruthyPos := c.emit(code.OpJumpNotTruthy, 9999) + + err = c.Compile(node.Consequence) + if err != nil { + return err + } + + if c.lastInstructionIsPop() { + c.removeLastPop() + } + + // Emit an `OpJump` with a bogus value + jumpPos := c.emit(code.OpJump, 9999) + + afterConsequencePos := len(c.instructions) + c.changeOperand(jumpNotTruthyPos, afterConsequencePos) + + if node.Alternative == nil { + c.emit(code.OpNull) + } else { + err := c.Compile(node.Alternative) + if err != nil { + return err + } + + if c.lastInstructionIsPop() { + c.removeLastPop() + } + } + + afterAlternativePos := len(c.instructions) + c.changeOperand(jumpPos, afterAlternativePos) + + case *ast.BlockStatement: + for _, s := range node.Statements { + err := c.Compile(s) + if err != nil { + return err + } + } + + case *ast.LetStatement: + err := c.Compile(node.Value) + if err != nil { + return err + } + symbol := c.symbolTable.Define(node.Name.Value) + c.emit(code.OpSetGlobal, symbol.Index) + + case *ast.Identifier: + symbol, ok := c.symbolTable.Resolve(node.Value) + if !ok { + return fmt.Errorf("undefined variable %s", node.Value) + } + c.emit(code.OpGetGlobal, symbol.Index) + + } + + return nil +} + +func (c *Compiler) Bytecode() *Bytecode { + return &Bytecode{ + Instructions: c.instructions, + Constants: c.constants, + } +} + +func (c *Compiler) addConstant(obj object.Object) int { + c.constants = append(c.constants, obj) + return len(c.constants) - 1 +} + +func (c *Compiler) emit(op code.Opcode, operands ...int) int { + ins := code.Make(op, operands...) + pos := c.addInstruction(ins) + + c.setLastInstruction(op, pos) + + return pos +} + +func (c *Compiler) addInstruction(ins []byte) int { + posNewInstruction := len(c.instructions) + c.instructions = append(c.instructions, ins...) + return posNewInstruction +} + +func (c *Compiler) setLastInstruction(op code.Opcode, pos int) { + previous := c.lastInstruction + last := EmittedInstruction{Opcode: op, Position: pos} + + c.previousInstruction = previous + c.lastInstruction = last +} + +func (c *Compiler) lastInstructionIsPop() bool { + return c.lastInstruction.Opcode == code.OpPop +} + +func (c *Compiler) removeLastPop() { + c.instructions = c.instructions[:c.lastInstruction.Position] + c.lastInstruction = c.previousInstruction +} + +func (c *Compiler) replaceInstruction(pos int, newInstruction []byte) { + for i := 0; i < len(newInstruction); i++ { + c.instructions[pos+i] = newInstruction[i] + } +} + +func (c *Compiler) changeOperand(opPos int, operand int) { + op := code.Opcode(c.instructions[opPos]) + newInstruction := code.Make(op, operand) + + c.replaceInstruction(opPos, newInstruction) +} + +type Bytecode struct { + Instructions code.Instructions + Constants []object.Object +} + +type EmittedInstruction struct { + Opcode code.Opcode + Position int +} diff --git a/wcig_code_1_2/05/src/monkey/compiler/compiler_test.go b/wcig_code_1_2/05/src/monkey/compiler/compiler_test.go new file mode 100644 index 0000000..3e5ff6a --- /dev/null +++ b/wcig_code_1_2/05/src/monkey/compiler/compiler_test.go @@ -0,0 +1,381 @@ +package compiler + +import ( + "fmt" + "monkey/ast" + "monkey/code" + "monkey/lexer" + "monkey/object" + "monkey/parser" + "testing" +) + +func TestIntegerArithmetic(t *testing.T) { + tests := []compilerTestCase{ + { + input: "1 + 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpAdd), + code.Make(code.OpPop), + }, + }, + { + input: "1; 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpPop), + code.Make(code.OpConstant, 1), + code.Make(code.OpPop), + }, + }, + { + input: "1 - 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpSub), + code.Make(code.OpPop), + }, + }, + { + input: "1 * 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpMul), + code.Make(code.OpPop), + }, + }, + { + input: "2 / 1", + expectedConstants: []interface{}{2, 1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpDiv), + code.Make(code.OpPop), + }, + }, + { + input: "-1", + expectedConstants: []interface{}{1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpMinus), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestBooleanExpressions(t *testing.T) { + tests := []compilerTestCase{ + { + input: "true", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpTrue), + code.Make(code.OpPop), + }, + }, + { + input: "false", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpFalse), + code.Make(code.OpPop), + }, + }, + { + input: "1 > 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpGreaterThan), + code.Make(code.OpPop), + }, + }, + { + input: "1 < 2", + expectedConstants: []interface{}{2, 1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpGreaterThan), + code.Make(code.OpPop), + }, + }, + { + input: "1 == 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpEqual), + code.Make(code.OpPop), + }, + }, + { + input: "1 != 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpNotEqual), + code.Make(code.OpPop), + }, + }, + { + input: "true == false", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpTrue), + code.Make(code.OpFalse), + code.Make(code.OpEqual), + code.Make(code.OpPop), + }, + }, + { + input: "true != false", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpTrue), + code.Make(code.OpFalse), + code.Make(code.OpNotEqual), + code.Make(code.OpPop), + }, + }, + { + input: "!true", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpTrue), + code.Make(code.OpBang), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestConditionals(t *testing.T) { + tests := []compilerTestCase{ + { + input: ` + if (true) { 10 }; 3333; + `, + expectedConstants: []interface{}{10, 3333}, + expectedInstructions: []code.Instructions{ + // 0000 + code.Make(code.OpTrue), + // 0001 + code.Make(code.OpJumpNotTruthy, 10), + // 0004 + code.Make(code.OpConstant, 0), + // 0007 + code.Make(code.OpJump, 11), + // 0010 + code.Make(code.OpNull), + // 0011 + code.Make(code.OpPop), + // 0012 + code.Make(code.OpConstant, 1), + // 0015 + code.Make(code.OpPop), + }, + }, + { + input: ` + if (true) { 10 } else { 20 }; 3333; + `, + expectedConstants: []interface{}{10, 20, 3333}, + expectedInstructions: []code.Instructions{ + // 0000 + code.Make(code.OpTrue), + // 0001 + code.Make(code.OpJumpNotTruthy, 10), + // 0004 + code.Make(code.OpConstant, 0), + // 0007 + code.Make(code.OpJump, 13), + // 0010 + code.Make(code.OpConstant, 1), + // 0013 + code.Make(code.OpPop), + // 0014 + code.Make(code.OpConstant, 2), + // 0017 + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestGlobalLetStatements(t *testing.T) { + tests := []compilerTestCase{ + { + input: ` + let one = 1; + let two = 2; + `, + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpSetGlobal, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpSetGlobal, 1), + }, + }, + { + input: ` + let one = 1; + one; + `, + expectedConstants: []interface{}{1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpSetGlobal, 0), + code.Make(code.OpGetGlobal, 0), + code.Make(code.OpPop), + }, + }, + { + input: ` + let one = 1; + let two = one; + two; + `, + expectedConstants: []interface{}{1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpSetGlobal, 0), + code.Make(code.OpGetGlobal, 0), + code.Make(code.OpSetGlobal, 1), + code.Make(code.OpGetGlobal, 1), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +type compilerTestCase struct { + input string + expectedConstants []interface{} + expectedInstructions []code.Instructions +} + +func runCompilerTests(t *testing.T, tests []compilerTestCase) { + t.Helper() + + for _, tt := range tests { + program := parse(tt.input) + + compiler := New() + err := compiler.Compile(program) + if err != nil { + t.Fatalf("compiler error: %s", err) + } + + bytecode := compiler.Bytecode() + + err = testInstructions(tt.expectedInstructions, bytecode.Instructions) + if err != nil { + t.Fatalf("testInstructions failed: %s", err) + } + + err = testConstants(t, tt.expectedConstants, bytecode.Constants) + if err != nil { + t.Fatalf("testConstants failed: %s", err) + } + } +} + +func parse(input string) *ast.Program { + l := lexer.New(input) + p := parser.New(l) + return p.ParseProgram() +} + +func testInstructions( + expected []code.Instructions, + actual code.Instructions, +) error { + concatted := concatInstructions(expected) + + if len(actual) != len(concatted) { + return fmt.Errorf("wrong instructions length.\nwant=%q\ngot =%q", + concatted, actual) + } + + for i, ins := range concatted { + if actual[i] != ins { + return fmt.Errorf("wrong instruction at %d.\nwant=%q\ngot =%q", + i, concatted, actual) + } + } + + return nil +} + +func concatInstructions(s []code.Instructions) code.Instructions { + out := code.Instructions{} + + for _, ins := range s { + out = append(out, ins...) + } + + return out +} + +func testConstants( + t *testing.T, + expected []interface{}, + actual []object.Object, +) error { + if len(expected) != len(actual) { + return fmt.Errorf("wrong number of constants. got=%d, want=%d", + len(actual), len(expected)) + } + + for i, constant := range expected { + switch constant := constant.(type) { + case int: + err := testIntegerObject(int64(constant), actual[i]) + if err != nil { + return fmt.Errorf("constant %d - testIntegerObject failed: %s", + i, err) + } + } + } + + return nil +} + +func testIntegerObject(expected int64, actual object.Object) error { + result, ok := actual.(*object.Integer) + if !ok { + return fmt.Errorf("object is not Integer. got=%T (%+v)", + actual, actual) + } + + if result.Value != expected { + return fmt.Errorf("object has wrong value. got=%d, want=%d", + result.Value, expected) + } + + return nil +} diff --git a/wcig_code_1_2/05/src/monkey/compiler/symbol_table.go b/wcig_code_1_2/05/src/monkey/compiler/symbol_table.go new file mode 100644 index 0000000..80303a7 --- /dev/null +++ b/wcig_code_1_2/05/src/monkey/compiler/symbol_table.go @@ -0,0 +1,35 @@ +package compiler + +type SymbolScope string + +const ( + GlobalScope SymbolScope = "GLOBAL" +) + +type Symbol struct { + Name string + Scope SymbolScope + Index int +} + +type SymbolTable struct { + store map[string]Symbol + numDefinitions int +} + +func NewSymbolTable() *SymbolTable { + s := make(map[string]Symbol) + return &SymbolTable{store: s} +} + +func (s *SymbolTable) Define(name string) Symbol { + symbol := Symbol{Name: name, Index: s.numDefinitions, Scope: GlobalScope} + s.store[name] = symbol + s.numDefinitions++ + return symbol +} + +func (s *SymbolTable) Resolve(name string) (Symbol, bool) { + obj, ok := s.store[name] + return obj, ok +} diff --git a/wcig_code_1_2/05/src/monkey/compiler/symbol_table_test.go b/wcig_code_1_2/05/src/monkey/compiler/symbol_table_test.go new file mode 100644 index 0000000..c037afc --- /dev/null +++ b/wcig_code_1_2/05/src/monkey/compiler/symbol_table_test.go @@ -0,0 +1,45 @@ +package compiler + +import "testing" + +func TestDefine(t *testing.T) { + expected := map[string]Symbol{ + "a": Symbol{Name: "a", Scope: GlobalScope, Index: 0}, + "b": Symbol{Name: "b", Scope: GlobalScope, Index: 1}, + } + + global := NewSymbolTable() + + a := global.Define("a") + if a != expected["a"] { + t.Errorf("expected a=%+v, got=%+v", expected["a"], a) + } + + b := global.Define("b") + if b != expected["b"] { + t.Errorf("expected b=%+v, got=%+v", expected["b"], b) + } +} + +func TestResolveGlobal(t *testing.T) { + global := NewSymbolTable() + global.Define("a") + global.Define("b") + + expected := []Symbol{ + Symbol{Name: "a", Scope: GlobalScope, Index: 0}, + Symbol{Name: "b", Scope: GlobalScope, Index: 1}, + } + + for _, sym := range expected { + result, ok := global.Resolve(sym.Name) + if !ok { + t.Errorf("name %s not resolvable", sym.Name) + continue + } + if result != sym { + t.Errorf("expected %s to resolve to %+v, got=%+v", + sym.Name, sym, result) + } + } +} diff --git a/wcig_code_1_2/05/src/monkey/evaluator/builtins.go b/wcig_code_1_2/05/src/monkey/evaluator/builtins.go new file mode 100644 index 0000000..c4fb0f3 --- /dev/null +++ b/wcig_code_1_2/05/src/monkey/evaluator/builtins.go @@ -0,0 +1,117 @@ +package evaluator + +import ( + "fmt" + "monkey/object" +) + +var builtins = map[string]*object.Builtin{ + "len": &object.Builtin{Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", + len(args)) + } + + switch arg := args[0].(type) { + case *object.Array: + return &object.Integer{Value: int64(len(arg.Elements))} + case *object.String: + return &object.Integer{Value: int64(len(arg.Value))} + default: + return newError("argument to `len` not supported, got %s", + args[0].Type()) + } + }, + }, + "puts": &object.Builtin{ + Fn: func(args ...object.Object) object.Object { + for _, arg := range args { + fmt.Println(arg.Inspect()) + } + + return NULL + }, + }, + "first": &object.Builtin{ + Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", + len(args)) + } + if args[0].Type() != object.ARRAY_OBJ { + return newError("argument to `first` must be ARRAY, got %s", + args[0].Type()) + } + + arr := args[0].(*object.Array) + if len(arr.Elements) > 0 { + return arr.Elements[0] + } + + return NULL + }, + }, + "last": &object.Builtin{ + Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", + len(args)) + } + if args[0].Type() != object.ARRAY_OBJ { + return newError("argument to `last` must be ARRAY, got %s", + args[0].Type()) + } + + arr := args[0].(*object.Array) + length := len(arr.Elements) + if length > 0 { + return arr.Elements[length-1] + } + + return NULL + }, + }, + "rest": &object.Builtin{ + Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", + len(args)) + } + if args[0].Type() != object.ARRAY_OBJ { + return newError("argument to `rest` must be ARRAY, got %s", + args[0].Type()) + } + + arr := args[0].(*object.Array) + length := len(arr.Elements) + if length > 0 { + newElements := make([]object.Object, length-1, length-1) + copy(newElements, arr.Elements[1:length]) + return &object.Array{Elements: newElements} + } + + return NULL + }, + }, + "push": &object.Builtin{ + Fn: func(args ...object.Object) object.Object { + if len(args) != 2 { + return newError("wrong number of arguments. got=%d, want=2", + len(args)) + } + if args[0].Type() != object.ARRAY_OBJ { + return newError("argument to `push` must be ARRAY, got %s", + args[0].Type()) + } + + arr := args[0].(*object.Array) + length := len(arr.Elements) + + newElements := make([]object.Object, length+1, length+1) + copy(newElements, arr.Elements) + newElements[length] = args[1] + + return &object.Array{Elements: newElements} + }, + }, +} diff --git a/wcig_code_1_2/05/src/monkey/evaluator/evaluator.go b/wcig_code_1_2/05/src/monkey/evaluator/evaluator.go new file mode 100644 index 0000000..50b2ab5 --- /dev/null +++ b/wcig_code_1_2/05/src/monkey/evaluator/evaluator.go @@ -0,0 +1,442 @@ +package evaluator + +import ( + "fmt" + "monkey/ast" + "monkey/object" +) + +var ( + NULL = &object.Null{} + TRUE = &object.Boolean{Value: true} + FALSE = &object.Boolean{Value: false} +) + +func Eval(node ast.Node, env *object.Environment) object.Object { + switch node := node.(type) { + + // Statements + case *ast.Program: + return evalProgram(node, env) + + case *ast.BlockStatement: + return evalBlockStatement(node, env) + + case *ast.ExpressionStatement: + return Eval(node.Expression, env) + + case *ast.ReturnStatement: + val := Eval(node.ReturnValue, env) + if isError(val) { + return val + } + return &object.ReturnValue{Value: val} + + case *ast.LetStatement: + val := Eval(node.Value, env) + if isError(val) { + return val + } + env.Set(node.Name.Value, val) + + // Expressions + case *ast.IntegerLiteral: + return &object.Integer{Value: node.Value} + + case *ast.StringLiteral: + return &object.String{Value: node.Value} + + case *ast.Boolean: + return nativeBoolToBooleanObject(node.Value) + + case *ast.PrefixExpression: + right := Eval(node.Right, env) + if isError(right) { + return right + } + return evalPrefixExpression(node.Operator, right) + + case *ast.InfixExpression: + left := Eval(node.Left, env) + if isError(left) { + return left + } + + right := Eval(node.Right, env) + if isError(right) { + return right + } + + return evalInfixExpression(node.Operator, left, right) + + case *ast.IfExpression: + return evalIfExpression(node, env) + + case *ast.Identifier: + return evalIdentifier(node, env) + + case *ast.FunctionLiteral: + params := node.Parameters + body := node.Body + return &object.Function{Parameters: params, Env: env, Body: body} + + case *ast.CallExpression: + function := Eval(node.Function, env) + if isError(function) { + return function + } + + args := evalExpressions(node.Arguments, env) + if len(args) == 1 && isError(args[0]) { + return args[0] + } + + return applyFunction(function, args) + + case *ast.ArrayLiteral: + elements := evalExpressions(node.Elements, env) + if len(elements) == 1 && isError(elements[0]) { + return elements[0] + } + return &object.Array{Elements: elements} + + case *ast.IndexExpression: + left := Eval(node.Left, env) + if isError(left) { + return left + } + index := Eval(node.Index, env) + if isError(index) { + return index + } + return evalIndexExpression(left, index) + + case *ast.HashLiteral: + return evalHashLiteral(node, env) + + } + + return nil +} + +func evalProgram(program *ast.Program, env *object.Environment) object.Object { + var result object.Object + + for _, statement := range program.Statements { + result = Eval(statement, env) + + switch result := result.(type) { + case *object.ReturnValue: + return result.Value + case *object.Error: + return result + } + } + + return result +} + +func evalBlockStatement( + block *ast.BlockStatement, + env *object.Environment, +) object.Object { + var result object.Object + + for _, statement := range block.Statements { + result = Eval(statement, env) + + if result != nil { + rt := result.Type() + if rt == object.RETURN_VALUE_OBJ || rt == object.ERROR_OBJ { + return result + } + } + } + + return result +} + +func nativeBoolToBooleanObject(input bool) *object.Boolean { + if input { + return TRUE + } + return FALSE +} + +func evalPrefixExpression(operator string, right object.Object) object.Object { + switch operator { + case "!": + return evalBangOperatorExpression(right) + case "-": + return evalMinusPrefixOperatorExpression(right) + default: + return newError("unknown operator: %s%s", operator, right.Type()) + } +} + +func evalInfixExpression( + operator string, + left, right object.Object, +) object.Object { + switch { + case left.Type() == object.INTEGER_OBJ && right.Type() == object.INTEGER_OBJ: + return evalIntegerInfixExpression(operator, left, right) + case left.Type() == object.STRING_OBJ && right.Type() == object.STRING_OBJ: + return evalStringInfixExpression(operator, left, right) + case operator == "==": + return nativeBoolToBooleanObject(left == right) + case operator == "!=": + return nativeBoolToBooleanObject(left != right) + case left.Type() != right.Type(): + return newError("type mismatch: %s %s %s", + left.Type(), operator, right.Type()) + default: + return newError("unknown operator: %s %s %s", + left.Type(), operator, right.Type()) + } +} + +func evalBangOperatorExpression(right object.Object) object.Object { + switch right { + case TRUE: + return FALSE + case FALSE: + return TRUE + case NULL: + return TRUE + default: + return FALSE + } +} + +func evalMinusPrefixOperatorExpression(right object.Object) object.Object { + if right.Type() != object.INTEGER_OBJ { + return newError("unknown operator: -%s", right.Type()) + } + + value := right.(*object.Integer).Value + return &object.Integer{Value: -value} +} + +func evalIntegerInfixExpression( + operator string, + left, right object.Object, +) object.Object { + leftVal := left.(*object.Integer).Value + rightVal := right.(*object.Integer).Value + + switch operator { + case "+": + return &object.Integer{Value: leftVal + rightVal} + case "-": + return &object.Integer{Value: leftVal - rightVal} + case "*": + return &object.Integer{Value: leftVal * rightVal} + case "/": + return &object.Integer{Value: leftVal / rightVal} + case "<": + return nativeBoolToBooleanObject(leftVal < rightVal) + case ">": + return nativeBoolToBooleanObject(leftVal > rightVal) + case "==": + return nativeBoolToBooleanObject(leftVal == rightVal) + case "!=": + return nativeBoolToBooleanObject(leftVal != rightVal) + default: + return newError("unknown operator: %s %s %s", + left.Type(), operator, right.Type()) + } +} + +func evalStringInfixExpression( + operator string, + left, right object.Object, +) object.Object { + if operator != "+" { + return newError("unknown operator: %s %s %s", + left.Type(), operator, right.Type()) + } + + leftVal := left.(*object.String).Value + rightVal := right.(*object.String).Value + return &object.String{Value: leftVal + rightVal} +} + +func evalIfExpression( + ie *ast.IfExpression, + env *object.Environment, +) object.Object { + condition := Eval(ie.Condition, env) + if isError(condition) { + return condition + } + + if isTruthy(condition) { + return Eval(ie.Consequence, env) + } else if ie.Alternative != nil { + return Eval(ie.Alternative, env) + } else { + return NULL + } +} + +func evalIdentifier( + node *ast.Identifier, + env *object.Environment, +) object.Object { + if val, ok := env.Get(node.Value); ok { + return val + } + + if builtin, ok := builtins[node.Value]; ok { + return builtin + } + + return newError("identifier not found: " + node.Value) +} + +func isTruthy(obj object.Object) bool { + switch obj { + case NULL: + return false + case TRUE: + return true + case FALSE: + return false + default: + return true + } +} + +func newError(format string, a ...interface{}) *object.Error { + return &object.Error{Message: fmt.Sprintf(format, a...)} +} + +func isError(obj object.Object) bool { + if obj != nil { + return obj.Type() == object.ERROR_OBJ + } + return false +} + +func evalExpressions( + exps []ast.Expression, + env *object.Environment, +) []object.Object { + var result []object.Object + + for _, e := range exps { + evaluated := Eval(e, env) + if isError(evaluated) { + return []object.Object{evaluated} + } + result = append(result, evaluated) + } + + return result +} + +func applyFunction(fn object.Object, args []object.Object) object.Object { + switch fn := fn.(type) { + + case *object.Function: + extendedEnv := extendFunctionEnv(fn, args) + evaluated := Eval(fn.Body, extendedEnv) + return unwrapReturnValue(evaluated) + + case *object.Builtin: + return fn.Fn(args...) + + default: + return newError("not a function: %s", fn.Type()) + } +} + +func extendFunctionEnv( + fn *object.Function, + args []object.Object, +) *object.Environment { + env := object.NewEnclosedEnvironment(fn.Env) + + for paramIdx, param := range fn.Parameters { + env.Set(param.Value, args[paramIdx]) + } + + return env +} + +func unwrapReturnValue(obj object.Object) object.Object { + if returnValue, ok := obj.(*object.ReturnValue); ok { + return returnValue.Value + } + + return obj +} + +func evalIndexExpression(left, index object.Object) object.Object { + switch { + case left.Type() == object.ARRAY_OBJ && index.Type() == object.INTEGER_OBJ: + return evalArrayIndexExpression(left, index) + case left.Type() == object.HASH_OBJ: + return evalHashIndexExpression(left, index) + default: + return newError("index operator not supported: %s", left.Type()) + } +} + +func evalArrayIndexExpression(array, index object.Object) object.Object { + arrayObject := array.(*object.Array) + idx := index.(*object.Integer).Value + max := int64(len(arrayObject.Elements) - 1) + + if idx < 0 || idx > max { + return NULL + } + + return arrayObject.Elements[idx] +} + +func evalHashLiteral( + node *ast.HashLiteral, + env *object.Environment, +) object.Object { + pairs := make(map[object.HashKey]object.HashPair) + + for keyNode, valueNode := range node.Pairs { + key := Eval(keyNode, env) + if isError(key) { + return key + } + + hashKey, ok := key.(object.Hashable) + if !ok { + return newError("unusable as hash key: %s", key.Type()) + } + + value := Eval(valueNode, env) + if isError(value) { + return value + } + + hashed := hashKey.HashKey() + pairs[hashed] = object.HashPair{Key: key, Value: value} + } + + return &object.Hash{Pairs: pairs} +} + +func evalHashIndexExpression(hash, index object.Object) object.Object { + hashObject := hash.(*object.Hash) + + key, ok := index.(object.Hashable) + if !ok { + return newError("unusable as hash key: %s", index.Type()) + } + + pair, ok := hashObject.Pairs[key.HashKey()] + if !ok { + return NULL + } + + return pair.Value +} diff --git a/wcig_code_1_2/05/src/monkey/evaluator/evaluator_test.go b/wcig_code_1_2/05/src/monkey/evaluator/evaluator_test.go new file mode 100644 index 0000000..2304e6f --- /dev/null +++ b/wcig_code_1_2/05/src/monkey/evaluator/evaluator_test.go @@ -0,0 +1,629 @@ +package evaluator + +import ( + "monkey/lexer" + "monkey/object" + "monkey/parser" + "testing" +) + +func TestEvalIntegerExpression(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"5", 5}, + {"10", 10}, + {"-5", -5}, + {"-10", -10}, + {"5 + 5 + 5 + 5 - 10", 10}, + {"2 * 2 * 2 * 2 * 2", 32}, + {"-50 + 100 + -50", 0}, + {"5 * 2 + 10", 20}, + {"5 + 2 * 10", 25}, + {"20 + 2 * -10", 0}, + {"50 / 2 * 2 + 10", 60}, + {"2 * (5 + 10)", 30}, + {"3 * 3 * 3 + 10", 37}, + {"3 * (3 * 3) + 10", 37}, + {"(5 + 10 * 2 + 15 / 3) * 2 + -10", 50}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testIntegerObject(t, evaluated, tt.expected) + } +} + +func TestEvalBooleanExpression(t *testing.T) { + tests := []struct { + input string + expected bool + }{ + {"true", true}, + {"false", false}, + {"1 < 2", true}, + {"1 > 2", false}, + {"1 < 1", false}, + {"1 > 1", false}, + {"1 == 1", true}, + {"1 != 1", false}, + {"1 == 2", false}, + {"1 != 2", true}, + {"true == true", true}, + {"false == false", true}, + {"true == false", false}, + {"true != false", true}, + {"false != true", true}, + {"(1 < 2) == true", true}, + {"(1 < 2) == false", false}, + {"(1 > 2) == true", false}, + {"(1 > 2) == false", true}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testBooleanObject(t, evaluated, tt.expected) + } +} + +func TestBangOperator(t *testing.T) { + tests := []struct { + input string + expected bool + }{ + {"!true", false}, + {"!false", true}, + {"!5", false}, + {"!!true", true}, + {"!!false", false}, + {"!!5", true}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testBooleanObject(t, evaluated, tt.expected) + } +} + +func TestIfElseExpressions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + {"if (true) { 10 }", 10}, + {"if (false) { 10 }", nil}, + {"if (1) { 10 }", 10}, + {"if (1 < 2) { 10 }", 10}, + {"if (1 > 2) { 10 }", nil}, + {"if (1 > 2) { 10 } else { 20 }", 20}, + {"if (1 < 2) { 10 } else { 20 }", 10}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + integer, ok := tt.expected.(int) + if ok { + testIntegerObject(t, evaluated, int64(integer)) + } else { + testNullObject(t, evaluated) + } + } +} + +func TestReturnStatements(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"return 10;", 10}, + {"return 10; 9;", 10}, + {"return 2 * 5; 9;", 10}, + {"9; return 2 * 5; 9;", 10}, + {"if (10 > 1) { return 10; }", 10}, + { + ` +if (10 > 1) { + if (10 > 1) { + return 10; + } + + return 1; +} +`, + 10, + }, + { + ` +let f = fn(x) { + return x; + x + 10; +}; +f(10);`, + 10, + }, + { + ` +let f = fn(x) { + let result = x + 10; + return result; + return 10; +}; +f(10);`, + 20, + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testIntegerObject(t, evaluated, tt.expected) + } +} + +func TestErrorHandling(t *testing.T) { + tests := []struct { + input string + expectedMessage string + }{ + { + "5 + true;", + "type mismatch: INTEGER + BOOLEAN", + }, + { + "5 + true; 5;", + "type mismatch: INTEGER + BOOLEAN", + }, + { + "-true", + "unknown operator: -BOOLEAN", + }, + { + "true + false;", + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + "true + false + true + false;", + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + "5; true + false; 5", + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + `"Hello" - "World"`, + "unknown operator: STRING - STRING", + }, + { + "if (10 > 1) { true + false; }", + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + ` +if (10 > 1) { + if (10 > 1) { + return true + false; + } + + return 1; +} +`, + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + "foobar", + "identifier not found: foobar", + }, + { + `{"name": "Monkey"}[fn(x) { x }];`, + "unusable as hash key: FUNCTION", + }, + { + `999[1]`, + "index operator not supported: INTEGER", + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + + errObj, ok := evaluated.(*object.Error) + if !ok { + t.Errorf("no error object returned. got=%T(%+v)", + evaluated, evaluated) + continue + } + + if errObj.Message != tt.expectedMessage { + t.Errorf("wrong error message. expected=%q, got=%q", + tt.expectedMessage, errObj.Message) + } + } +} + +func TestLetStatements(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"let a = 5; a;", 5}, + {"let a = 5 * 5; a;", 25}, + {"let a = 5; let b = a; b;", 5}, + {"let a = 5; let b = a; let c = a + b + 5; c;", 15}, + } + + for _, tt := range tests { + testIntegerObject(t, testEval(tt.input), tt.expected) + } +} + +func TestFunctionObject(t *testing.T) { + input := "fn(x) { x + 2; };" + + evaluated := testEval(input) + fn, ok := evaluated.(*object.Function) + if !ok { + t.Fatalf("object is not Function. got=%T (%+v)", evaluated, evaluated) + } + + if len(fn.Parameters) != 1 { + t.Fatalf("function has wrong parameters. Parameters=%+v", + fn.Parameters) + } + + if fn.Parameters[0].String() != "x" { + t.Fatalf("parameter is not 'x'. got=%q", fn.Parameters[0]) + } + + expectedBody := "(x + 2)" + + if fn.Body.String() != expectedBody { + t.Fatalf("body is not %q. got=%q", expectedBody, fn.Body.String()) + } +} + +func TestFunctionApplication(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"let identity = fn(x) { x; }; identity(5);", 5}, + {"let identity = fn(x) { return x; }; identity(5);", 5}, + {"let double = fn(x) { x * 2; }; double(5);", 10}, + {"let add = fn(x, y) { x + y; }; add(5, 5);", 10}, + {"let add = fn(x, y) { x + y; }; add(5 + 5, add(5, 5));", 20}, + {"fn(x) { x; }(5)", 5}, + } + + for _, tt := range tests { + testIntegerObject(t, testEval(tt.input), tt.expected) + } +} + +func TestEnclosingEnvironments(t *testing.T) { + input := ` +let first = 10; +let second = 10; +let third = 10; + +let ourFunction = fn(first) { + let second = 20; + + first + second + third; +}; + +ourFunction(20) + first + second;` + + testIntegerObject(t, testEval(input), 70) +} + +func TestClosures(t *testing.T) { + input := ` +let newAdder = fn(x) { + fn(y) { x + y }; +}; + +let addTwo = newAdder(2); +addTwo(2);` + + testIntegerObject(t, testEval(input), 4) +} + +func TestStringLiteral(t *testing.T) { + input := `"Hello World!"` + + evaluated := testEval(input) + str, ok := evaluated.(*object.String) + if !ok { + t.Fatalf("object is not String. got=%T (%+v)", evaluated, evaluated) + } + + if str.Value != "Hello World!" { + t.Errorf("String has wrong value. got=%q", str.Value) + } +} + +func TestStringConcatenation(t *testing.T) { + input := `"Hello" + " " + "World!"` + + evaluated := testEval(input) + str, ok := evaluated.(*object.String) + if !ok { + t.Fatalf("object is not String. got=%T (%+v)", evaluated, evaluated) + } + + if str.Value != "Hello World!" { + t.Errorf("String has wrong value. got=%q", str.Value) + } +} + +func TestBuiltinFunctions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + {`len("")`, 0}, + {`len("four")`, 4}, + {`len("hello world")`, 11}, + {`len(1)`, "argument to `len` not supported, got INTEGER"}, + {`len("one", "two")`, "wrong number of arguments. got=2, want=1"}, + {`len([1, 2, 3])`, 3}, + {`len([])`, 0}, + {`puts("hello", "world!")`, nil}, + {`first([1, 2, 3])`, 1}, + {`first([])`, nil}, + {`first(1)`, "argument to `first` must be ARRAY, got INTEGER"}, + {`last([1, 2, 3])`, 3}, + {`last([])`, nil}, + {`last(1)`, "argument to `last` must be ARRAY, got INTEGER"}, + {`rest([1, 2, 3])`, []int{2, 3}}, + {`rest([])`, nil}, + {`push([], 1)`, []int{1}}, + {`push(1, 1)`, "argument to `push` must be ARRAY, got INTEGER"}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + + switch expected := tt.expected.(type) { + case int: + testIntegerObject(t, evaluated, int64(expected)) + case nil: + testNullObject(t, evaluated) + case string: + errObj, ok := evaluated.(*object.Error) + if !ok { + t.Errorf("object is not Error. got=%T (%+v)", + evaluated, evaluated) + continue + } + if errObj.Message != expected { + t.Errorf("wrong error message. expected=%q, got=%q", + expected, errObj.Message) + } + case []int: + array, ok := evaluated.(*object.Array) + if !ok { + t.Errorf("obj not Array. got=%T (%+v)", evaluated, evaluated) + continue + } + + if len(array.Elements) != len(expected) { + t.Errorf("wrong num of elements. want=%d, got=%d", + len(expected), len(array.Elements)) + continue + } + + for i, expectedElem := range expected { + testIntegerObject(t, array.Elements[i], int64(expectedElem)) + } + } + } +} + +func TestArrayLiterals(t *testing.T) { + input := "[1, 2 * 2, 3 + 3]" + + evaluated := testEval(input) + result, ok := evaluated.(*object.Array) + if !ok { + t.Fatalf("object is not Array. got=%T (%+v)", evaluated, evaluated) + } + + if len(result.Elements) != 3 { + t.Fatalf("array has wrong num of elements. got=%d", + len(result.Elements)) + } + + testIntegerObject(t, result.Elements[0], 1) + testIntegerObject(t, result.Elements[1], 4) + testIntegerObject(t, result.Elements[2], 6) +} + +func TestArrayIndexExpressions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + { + "[1, 2, 3][0]", + 1, + }, + { + "[1, 2, 3][1]", + 2, + }, + { + "[1, 2, 3][2]", + 3, + }, + { + "let i = 0; [1][i];", + 1, + }, + { + "[1, 2, 3][1 + 1];", + 3, + }, + { + "let myArray = [1, 2, 3]; myArray[2];", + 3, + }, + { + "let myArray = [1, 2, 3]; myArray[0] + myArray[1] + myArray[2];", + 6, + }, + { + "let myArray = [1, 2, 3]; let i = myArray[0]; myArray[i]", + 2, + }, + { + "[1, 2, 3][3]", + nil, + }, + { + "[1, 2, 3][-1]", + nil, + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + integer, ok := tt.expected.(int) + if ok { + testIntegerObject(t, evaluated, int64(integer)) + } else { + testNullObject(t, evaluated) + } + } +} + +func TestHashLiterals(t *testing.T) { + input := `let two = "two"; + { + "one": 10 - 9, + two: 1 + 1, + "thr" + "ee": 6 / 2, + 4: 4, + true: 5, + false: 6 + }` + + evaluated := testEval(input) + result, ok := evaluated.(*object.Hash) + if !ok { + t.Fatalf("Eval didn't return Hash. got=%T (%+v)", evaluated, evaluated) + } + + expected := map[object.HashKey]int64{ + (&object.String{Value: "one"}).HashKey(): 1, + (&object.String{Value: "two"}).HashKey(): 2, + (&object.String{Value: "three"}).HashKey(): 3, + (&object.Integer{Value: 4}).HashKey(): 4, + TRUE.HashKey(): 5, + FALSE.HashKey(): 6, + } + + if len(result.Pairs) != len(expected) { + t.Fatalf("Hash has wrong num of pairs. got=%d", len(result.Pairs)) + } + + for expectedKey, expectedValue := range expected { + pair, ok := result.Pairs[expectedKey] + if !ok { + t.Errorf("no pair for given key in Pairs") + } + + testIntegerObject(t, pair.Value, expectedValue) + } +} + +func TestHashIndexExpressions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + { + `{"foo": 5}["foo"]`, + 5, + }, + { + `{"foo": 5}["bar"]`, + nil, + }, + { + `let key = "foo"; {"foo": 5}[key]`, + 5, + }, + { + `{}["foo"]`, + nil, + }, + { + `{5: 5}[5]`, + 5, + }, + { + `{true: 5}[true]`, + 5, + }, + { + `{false: 5}[false]`, + 5, + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + integer, ok := tt.expected.(int) + if ok { + testIntegerObject(t, evaluated, int64(integer)) + } else { + testNullObject(t, evaluated) + } + } +} +func testEval(input string) object.Object { + l := lexer.New(input) + p := parser.New(l) + program := p.ParseProgram() + env := object.NewEnvironment() + + return Eval(program, env) +} + +func testIntegerObject(t *testing.T, obj object.Object, expected int64) bool { + result, ok := obj.(*object.Integer) + if !ok { + t.Errorf("object is not Integer. got=%T (%+v)", obj, obj) + return false + } + if result.Value != expected { + t.Errorf("object has wrong value. got=%d, want=%d", + result.Value, expected) + return false + } + + return true +} + +func testBooleanObject(t *testing.T, obj object.Object, expected bool) bool { + result, ok := obj.(*object.Boolean) + if !ok { + t.Errorf("object is not Boolean. got=%T (%+v)", obj, obj) + return false + } + if result.Value != expected { + t.Errorf("object has wrong value. got=%t, want=%t", + result.Value, expected) + return false + } + return true +} + +func testNullObject(t *testing.T, obj object.Object) bool { + if obj != NULL { + t.Errorf("object is not NULL. got=%T (%+v)", obj, obj) + return false + } + return true +} diff --git a/wcig_code_1_2/05/src/monkey/go.mod b/wcig_code_1_2/05/src/monkey/go.mod new file mode 100644 index 0000000..3064854 --- /dev/null +++ b/wcig_code_1_2/05/src/monkey/go.mod @@ -0,0 +1,3 @@ +module monkey + +go 1.14 diff --git a/wcig_code_1_2/05/src/monkey/lexer/lexer.go b/wcig_code_1_2/05/src/monkey/lexer/lexer.go new file mode 100644 index 0000000..db4f5f7 --- /dev/null +++ b/wcig_code_1_2/05/src/monkey/lexer/lexer.go @@ -0,0 +1,157 @@ +package lexer + +import "monkey/token" + +type Lexer struct { + input string + position int // current position in input (points to current char) + readPosition int // current reading position in input (after current char) + ch byte // current char under examination +} + +func New(input string) *Lexer { + l := &Lexer{input: input} + l.readChar() + return l +} + +func (l *Lexer) NextToken() token.Token { + var tok token.Token + + l.skipWhitespace() + + switch l.ch { + case '=': + if l.peekChar() == '=' { + ch := l.ch + l.readChar() + literal := string(ch) + string(l.ch) + tok = token.Token{Type: token.EQ, Literal: literal} + } else { + tok = newToken(token.ASSIGN, l.ch) + } + case '+': + tok = newToken(token.PLUS, l.ch) + case '-': + tok = newToken(token.MINUS, l.ch) + case '!': + if l.peekChar() == '=' { + ch := l.ch + l.readChar() + literal := string(ch) + string(l.ch) + tok = token.Token{Type: token.NOT_EQ, Literal: literal} + } else { + tok = newToken(token.BANG, l.ch) + } + case '/': + tok = newToken(token.SLASH, l.ch) + case '*': + tok = newToken(token.ASTERISK, l.ch) + case '<': + tok = newToken(token.LT, l.ch) + case '>': + tok = newToken(token.GT, l.ch) + case ';': + tok = newToken(token.SEMICOLON, l.ch) + case ':': + tok = newToken(token.COLON, l.ch) + case ',': + tok = newToken(token.COMMA, l.ch) + case '{': + tok = newToken(token.LBRACE, l.ch) + case '}': + tok = newToken(token.RBRACE, l.ch) + case '(': + tok = newToken(token.LPAREN, l.ch) + case ')': + tok = newToken(token.RPAREN, l.ch) + case '"': + tok.Type = token.STRING + tok.Literal = l.readString() + case '[': + tok = newToken(token.LBRACKET, l.ch) + case ']': + tok = newToken(token.RBRACKET, l.ch) + case 0: + tok.Literal = "" + tok.Type = token.EOF + default: + if isLetter(l.ch) { + tok.Literal = l.readIdentifier() + tok.Type = token.LookupIdent(tok.Literal) + return tok + } else if isDigit(l.ch) { + tok.Type = token.INT + tok.Literal = l.readNumber() + return tok + } else { + tok = newToken(token.ILLEGAL, l.ch) + } + } + + l.readChar() + return tok +} + +func (l *Lexer) skipWhitespace() { + for l.ch == ' ' || l.ch == '\t' || l.ch == '\n' || l.ch == '\r' { + l.readChar() + } +} + +func (l *Lexer) readChar() { + if l.readPosition >= len(l.input) { + l.ch = 0 + } else { + l.ch = l.input[l.readPosition] + } + l.position = l.readPosition + l.readPosition += 1 +} + +func (l *Lexer) peekChar() byte { + if l.readPosition >= len(l.input) { + return 0 + } else { + return l.input[l.readPosition] + } +} + +func (l *Lexer) readIdentifier() string { + position := l.position + for isLetter(l.ch) { + l.readChar() + } + return l.input[position:l.position] +} + +func (l *Lexer) readNumber() string { + position := l.position + for isDigit(l.ch) { + l.readChar() + } + return l.input[position:l.position] +} + +func (l *Lexer) readString() string { + position := l.position + 1 + for { + l.readChar() + if l.ch == '"' || l.ch == 0 { + break + } + } + return l.input[position:l.position] +} + +func isLetter(ch byte) bool { + return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' +} + +func isDigit(ch byte) bool { + return '0' <= ch && ch <= '9' +} + +func newToken(tokenType token.TokenType, ch byte) token.Token { + return token.Token{Type: tokenType, Literal: string(ch)} +} diff --git a/wcig_code_1_2/05/src/monkey/lexer/lexer_test.go b/wcig_code_1_2/05/src/monkey/lexer/lexer_test.go new file mode 100644 index 0000000..e4e64a4 --- /dev/null +++ b/wcig_code_1_2/05/src/monkey/lexer/lexer_test.go @@ -0,0 +1,143 @@ +package lexer + +import ( + "testing" + + "monkey/token" +) + +func TestNextToken(t *testing.T) { + input := `let five = 5; +let ten = 10; + +let add = fn(x, y) { + x + y; +}; + +let result = add(five, ten); +!-/*5; +5 < 10 > 5; + +if (5 < 10) { + return true; +} else { + return false; +} + +10 == 10; +10 != 9; +"foobar" +"foo bar" +[1, 2]; +{"foo": "bar"} +` + + tests := []struct { + expectedType token.TokenType + expectedLiteral string + }{ + {token.LET, "let"}, + {token.IDENT, "five"}, + {token.ASSIGN, "="}, + {token.INT, "5"}, + {token.SEMICOLON, ";"}, + {token.LET, "let"}, + {token.IDENT, "ten"}, + {token.ASSIGN, "="}, + {token.INT, "10"}, + {token.SEMICOLON, ";"}, + {token.LET, "let"}, + {token.IDENT, "add"}, + {token.ASSIGN, "="}, + {token.FUNCTION, "fn"}, + {token.LPAREN, "("}, + {token.IDENT, "x"}, + {token.COMMA, ","}, + {token.IDENT, "y"}, + {token.RPAREN, ")"}, + {token.LBRACE, "{"}, + {token.IDENT, "x"}, + {token.PLUS, "+"}, + {token.IDENT, "y"}, + {token.SEMICOLON, ";"}, + {token.RBRACE, "}"}, + {token.SEMICOLON, ";"}, + {token.LET, "let"}, + {token.IDENT, "result"}, + {token.ASSIGN, "="}, + {token.IDENT, "add"}, + {token.LPAREN, "("}, + {token.IDENT, "five"}, + {token.COMMA, ","}, + {token.IDENT, "ten"}, + {token.RPAREN, ")"}, + {token.SEMICOLON, ";"}, + {token.BANG, "!"}, + {token.MINUS, "-"}, + {token.SLASH, "/"}, + {token.ASTERISK, "*"}, + {token.INT, "5"}, + {token.SEMICOLON, ";"}, + {token.INT, "5"}, + {token.LT, "<"}, + {token.INT, "10"}, + {token.GT, ">"}, + {token.INT, "5"}, + {token.SEMICOLON, ";"}, + {token.IF, "if"}, + {token.LPAREN, "("}, + {token.INT, "5"}, + {token.LT, "<"}, + {token.INT, "10"}, + {token.RPAREN, ")"}, + {token.LBRACE, "{"}, + {token.RETURN, "return"}, + {token.TRUE, "true"}, + {token.SEMICOLON, ";"}, + {token.RBRACE, "}"}, + {token.ELSE, "else"}, + {token.LBRACE, "{"}, + {token.RETURN, "return"}, + {token.FALSE, "false"}, + {token.SEMICOLON, ";"}, + {token.RBRACE, "}"}, + {token.INT, "10"}, + {token.EQ, "=="}, + {token.INT, "10"}, + {token.SEMICOLON, ";"}, + {token.INT, "10"}, + {token.NOT_EQ, "!="}, + {token.INT, "9"}, + {token.SEMICOLON, ";"}, + {token.STRING, "foobar"}, + {token.STRING, "foo bar"}, + {token.LBRACKET, "["}, + {token.INT, "1"}, + {token.COMMA, ","}, + {token.INT, "2"}, + {token.RBRACKET, "]"}, + {token.SEMICOLON, ";"}, + {token.LBRACE, "{"}, + {token.STRING, "foo"}, + {token.COLON, ":"}, + {token.STRING, "bar"}, + {token.RBRACE, "}"}, + {token.EOF, ""}, + } + + l := New(input) + + for i, tt := range tests { + tok := l.NextToken() + + if tok.Type != tt.expectedType { + t.Fatalf("tests[%d] - tokentype wrong. expected=%q, got=%q", + i, tt.expectedType, tok.Type) + } + + if tok.Literal != tt.expectedLiteral { + t.Fatalf("tests[%d] - literal wrong. expected=%q, got=%q", + i, tt.expectedLiteral, tok.Literal) + } + } +} diff --git a/wcig_code_1_2/05/src/monkey/main.go b/wcig_code_1_2/05/src/monkey/main.go new file mode 100644 index 0000000..1d27138 --- /dev/null +++ b/wcig_code_1_2/05/src/monkey/main.go @@ -0,0 +1,19 @@ +package main + +import ( + "fmt" + "monkey/repl" + "os" + "os/user" +) + +func main() { + user, err := user.Current() + if err != nil { + panic(err) + } + fmt.Printf("Hello %s! This is the Monkey programming language!\n", + user.Username) + fmt.Printf("Feel free to type in commands\n") + repl.Start(os.Stdin, os.Stdout) +} diff --git a/wcig_code_1_2/05/src/monkey/object/environment.go b/wcig_code_1_2/05/src/monkey/object/environment.go new file mode 100644 index 0000000..6f31070 --- /dev/null +++ b/wcig_code_1_2/05/src/monkey/object/environment.go @@ -0,0 +1,30 @@ +package object + +func NewEnclosedEnvironment(outer *Environment) *Environment { + env := NewEnvironment() + env.outer = outer + return env +} + +func NewEnvironment() *Environment { + s := make(map[string]Object) + return &Environment{store: s, outer: nil} +} + +type Environment struct { + store map[string]Object + outer *Environment +} + +func (e *Environment) Get(name string) (Object, bool) { + obj, ok := e.store[name] + if !ok && e.outer != nil { + obj, ok = e.outer.Get(name) + } + return obj, ok +} + +func (e *Environment) Set(name string, val Object) Object { + e.store[name] = val + return val +} diff --git a/wcig_code_1_2/05/src/monkey/object/object.go b/wcig_code_1_2/05/src/monkey/object/object.go new file mode 100644 index 0000000..2c2a1b0 --- /dev/null +++ b/wcig_code_1_2/05/src/monkey/object/object.go @@ -0,0 +1,182 @@ +package object + +import ( + "bytes" + "fmt" + "hash/fnv" + "monkey/ast" + "strings" +) + +type BuiltinFunction func(args ...Object) Object + +type ObjectType string + +const ( + NULL_OBJ = "NULL" + ERROR_OBJ = "ERROR" + + INTEGER_OBJ = "INTEGER" + BOOLEAN_OBJ = "BOOLEAN" + STRING_OBJ = "STRING" + + RETURN_VALUE_OBJ = "RETURN_VALUE" + + FUNCTION_OBJ = "FUNCTION" + BUILTIN_OBJ = "BUILTIN" + + ARRAY_OBJ = "ARRAY" + HASH_OBJ = "HASH" +) + +type HashKey struct { + Type ObjectType + Value uint64 +} + +type Hashable interface { + HashKey() HashKey +} + +type Object interface { + Type() ObjectType + Inspect() string +} + +type Integer struct { + Value int64 +} + +func (i *Integer) Type() ObjectType { return INTEGER_OBJ } +func (i *Integer) Inspect() string { return fmt.Sprintf("%d", i.Value) } +func (i *Integer) HashKey() HashKey { + return HashKey{Type: i.Type(), Value: uint64(i.Value)} +} + +type Boolean struct { + Value bool +} + +func (b *Boolean) Type() ObjectType { return BOOLEAN_OBJ } +func (b *Boolean) Inspect() string { return fmt.Sprintf("%t", b.Value) } +func (b *Boolean) HashKey() HashKey { + var value uint64 + + if b.Value { + value = 1 + } else { + value = 0 + } + + return HashKey{Type: b.Type(), Value: value} +} + +type Null struct{} + +func (n *Null) Type() ObjectType { return NULL_OBJ } +func (n *Null) Inspect() string { return "null" } + +type ReturnValue struct { + Value Object +} + +func (rv *ReturnValue) Type() ObjectType { return RETURN_VALUE_OBJ } +func (rv *ReturnValue) Inspect() string { return rv.Value.Inspect() } + +type Error struct { + Message string +} + +func (e *Error) Type() ObjectType { return ERROR_OBJ } +func (e *Error) Inspect() string { return "ERROR: " + e.Message } + +type Function struct { + Parameters []*ast.Identifier + Body *ast.BlockStatement + Env *Environment +} + +func (f *Function) Type() ObjectType { return FUNCTION_OBJ } +func (f *Function) Inspect() string { + var out bytes.Buffer + + params := []string{} + for _, p := range f.Parameters { + params = append(params, p.String()) + } + + out.WriteString("fn") + out.WriteString("(") + out.WriteString(strings.Join(params, ", ")) + out.WriteString(") {\n") + out.WriteString(f.Body.String()) + out.WriteString("\n}") + + return out.String() +} + +type String struct { + Value string +} + +func (s *String) Type() ObjectType { return STRING_OBJ } +func (s *String) Inspect() string { return s.Value } +func (s *String) HashKey() HashKey { + h := fnv.New64a() + h.Write([]byte(s.Value)) + + return HashKey{Type: s.Type(), Value: h.Sum64()} +} + +type Builtin struct { + Fn BuiltinFunction +} + +func (b *Builtin) Type() ObjectType { return BUILTIN_OBJ } +func (b *Builtin) Inspect() string { return "builtin function" } + +type Array struct { + Elements []Object +} + +func (ao *Array) Type() ObjectType { return ARRAY_OBJ } +func (ao *Array) Inspect() string { + var out bytes.Buffer + + elements := []string{} + for _, e := range ao.Elements { + elements = append(elements, e.Inspect()) + } + + out.WriteString("[") + out.WriteString(strings.Join(elements, ", ")) + out.WriteString("]") + + return out.String() +} + +type HashPair struct { + Key Object + Value Object +} + +type Hash struct { + Pairs map[HashKey]HashPair +} + +func (h *Hash) Type() ObjectType { return HASH_OBJ } +func (h *Hash) Inspect() string { + var out bytes.Buffer + + pairs := []string{} + for _, pair := range h.Pairs { + pairs = append(pairs, fmt.Sprintf("%s: %s", + pair.Key.Inspect(), pair.Value.Inspect())) + } + + out.WriteString("{") + out.WriteString(strings.Join(pairs, ", ")) + out.WriteString("}") + + return out.String() +} diff --git a/wcig_code_1_2/05/src/monkey/object/object_test.go b/wcig_code_1_2/05/src/monkey/object/object_test.go new file mode 100644 index 0000000..63228a2 --- /dev/null +++ b/wcig_code_1_2/05/src/monkey/object/object_test.go @@ -0,0 +1,60 @@ +package object + +import "testing" + +func TestStringHashKey(t *testing.T) { + hello1 := &String{Value: "Hello World"} + hello2 := &String{Value: "Hello World"} + diff1 := &String{Value: "My name is johnny"} + diff2 := &String{Value: "My name is johnny"} + + if hello1.HashKey() != hello2.HashKey() { + t.Errorf("strings with same content have different hash keys") + } + + if diff1.HashKey() != diff2.HashKey() { + t.Errorf("strings with same content have different hash keys") + } + + if hello1.HashKey() == diff1.HashKey() { + t.Errorf("strings with different content have same hash keys") + } +} + +func TestBooleanHashKey(t *testing.T) { + true1 := &Boolean{Value: true} + true2 := &Boolean{Value: true} + false1 := &Boolean{Value: false} + false2 := &Boolean{Value: false} + + if true1.HashKey() != true2.HashKey() { + t.Errorf("trues do not have same hash key") + } + + if false1.HashKey() != false2.HashKey() { + t.Errorf("falses do not have same hash key") + } + + if true1.HashKey() == false1.HashKey() { + t.Errorf("true has same hash key as false") + } +} + +func TestIntegerHashKey(t *testing.T) { + one1 := &Integer{Value: 1} + one2 := &Integer{Value: 1} + two1 := &Integer{Value: 2} + two2 := &Integer{Value: 2} + + if one1.HashKey() != one2.HashKey() { + t.Errorf("integers with same content have twoerent hash keys") + } + + if two1.HashKey() != two2.HashKey() { + t.Errorf("integers with same content have twoerent hash keys") + } + + if one1.HashKey() == two1.HashKey() { + t.Errorf("integers with twoerent content have same hash keys") + } +} diff --git a/wcig_code_1_2/05/src/monkey/parser/parser.go b/wcig_code_1_2/05/src/monkey/parser/parser.go new file mode 100644 index 0000000..0a1f19c --- /dev/null +++ b/wcig_code_1_2/05/src/monkey/parser/parser.go @@ -0,0 +1,491 @@ +package parser + +import ( + "fmt" + "monkey/ast" + "monkey/lexer" + "monkey/token" + "strconv" +) + +const ( + _ int = iota + LOWEST + EQUALS // == + LESSGREATER // > or < + SUM // + + PRODUCT // * + PREFIX // -X or !X + CALL // myFunction(X) + INDEX // array[index] +) + +var precedences = map[token.TokenType]int{ + token.EQ: EQUALS, + token.NOT_EQ: EQUALS, + token.LT: LESSGREATER, + token.GT: LESSGREATER, + token.PLUS: SUM, + token.MINUS: SUM, + token.SLASH: PRODUCT, + token.ASTERISK: PRODUCT, + token.LPAREN: CALL, + token.LBRACKET: INDEX, +} + +type ( + prefixParseFn func() ast.Expression + infixParseFn func(ast.Expression) ast.Expression +) + +type Parser struct { + l *lexer.Lexer + errors []string + + curToken token.Token + peekToken token.Token + + prefixParseFns map[token.TokenType]prefixParseFn + infixParseFns map[token.TokenType]infixParseFn +} + +func New(l *lexer.Lexer) *Parser { + p := &Parser{ + l: l, + errors: []string{}, + } + + p.prefixParseFns = make(map[token.TokenType]prefixParseFn) + p.registerPrefix(token.IDENT, p.parseIdentifier) + p.registerPrefix(token.INT, p.parseIntegerLiteral) + p.registerPrefix(token.STRING, p.parseStringLiteral) + p.registerPrefix(token.BANG, p.parsePrefixExpression) + p.registerPrefix(token.MINUS, p.parsePrefixExpression) + p.registerPrefix(token.TRUE, p.parseBoolean) + p.registerPrefix(token.FALSE, p.parseBoolean) + p.registerPrefix(token.LPAREN, p.parseGroupedExpression) + p.registerPrefix(token.IF, p.parseIfExpression) + p.registerPrefix(token.FUNCTION, p.parseFunctionLiteral) + p.registerPrefix(token.LBRACKET, p.parseArrayLiteral) + p.registerPrefix(token.LBRACE, p.parseHashLiteral) + + p.infixParseFns = make(map[token.TokenType]infixParseFn) + p.registerInfix(token.PLUS, p.parseInfixExpression) + p.registerInfix(token.MINUS, p.parseInfixExpression) + p.registerInfix(token.SLASH, p.parseInfixExpression) + p.registerInfix(token.ASTERISK, p.parseInfixExpression) + p.registerInfix(token.EQ, p.parseInfixExpression) + p.registerInfix(token.NOT_EQ, p.parseInfixExpression) + p.registerInfix(token.LT, p.parseInfixExpression) + p.registerInfix(token.GT, p.parseInfixExpression) + + p.registerInfix(token.LPAREN, p.parseCallExpression) + p.registerInfix(token.LBRACKET, p.parseIndexExpression) + + // Read two tokens, so curToken and peekToken are both set + p.nextToken() + p.nextToken() + + return p +} + +func (p *Parser) nextToken() { + p.curToken = p.peekToken + p.peekToken = p.l.NextToken() +} + +func (p *Parser) curTokenIs(t token.TokenType) bool { + return p.curToken.Type == t +} + +func (p *Parser) peekTokenIs(t token.TokenType) bool { + return p.peekToken.Type == t +} + +func (p *Parser) expectPeek(t token.TokenType) bool { + if p.peekTokenIs(t) { + p.nextToken() + return true + } else { + p.peekError(t) + return false + } +} + +func (p *Parser) Errors() []string { + return p.errors +} + +func (p *Parser) peekError(t token.TokenType) { + msg := fmt.Sprintf("expected next token to be %s, got %s instead", + t, p.peekToken.Type) + p.errors = append(p.errors, msg) +} + +func (p *Parser) noPrefixParseFnError(t token.TokenType) { + msg := fmt.Sprintf("no prefix parse function for %s found", t) + p.errors = append(p.errors, msg) +} + +func (p *Parser) ParseProgram() *ast.Program { + program := &ast.Program{} + program.Statements = []ast.Statement{} + + for !p.curTokenIs(token.EOF) { + stmt := p.parseStatement() + if stmt != nil { + program.Statements = append(program.Statements, stmt) + } + p.nextToken() + } + + return program +} + +func (p *Parser) parseStatement() ast.Statement { + switch p.curToken.Type { + case token.LET: + return p.parseLetStatement() + case token.RETURN: + return p.parseReturnStatement() + default: + return p.parseExpressionStatement() + } +} + +func (p *Parser) parseLetStatement() *ast.LetStatement { + stmt := &ast.LetStatement{Token: p.curToken} + + if !p.expectPeek(token.IDENT) { + return nil + } + + stmt.Name = &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + + if !p.expectPeek(token.ASSIGN) { + return nil + } + + p.nextToken() + + stmt.Value = p.parseExpression(LOWEST) + + if p.peekTokenIs(token.SEMICOLON) { + p.nextToken() + } + + return stmt +} + +func (p *Parser) parseReturnStatement() *ast.ReturnStatement { + stmt := &ast.ReturnStatement{Token: p.curToken} + + p.nextToken() + + stmt.ReturnValue = p.parseExpression(LOWEST) + + if p.peekTokenIs(token.SEMICOLON) { + p.nextToken() + } + + return stmt +} + +func (p *Parser) parseExpressionStatement() *ast.ExpressionStatement { + stmt := &ast.ExpressionStatement{Token: p.curToken} + + stmt.Expression = p.parseExpression(LOWEST) + + if p.peekTokenIs(token.SEMICOLON) { + p.nextToken() + } + + return stmt +} + +func (p *Parser) parseExpression(precedence int) ast.Expression { + prefix := p.prefixParseFns[p.curToken.Type] + if prefix == nil { + p.noPrefixParseFnError(p.curToken.Type) + return nil + } + leftExp := prefix() + + for !p.peekTokenIs(token.SEMICOLON) && precedence < p.peekPrecedence() { + infix := p.infixParseFns[p.peekToken.Type] + if infix == nil { + return leftExp + } + + p.nextToken() + + leftExp = infix(leftExp) + } + + return leftExp +} + +func (p *Parser) peekPrecedence() int { + if p, ok := precedences[p.peekToken.Type]; ok { + return p + } + + return LOWEST +} + +func (p *Parser) curPrecedence() int { + if p, ok := precedences[p.curToken.Type]; ok { + return p + } + + return LOWEST +} + +func (p *Parser) parseIdentifier() ast.Expression { + return &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} +} + +func (p *Parser) parseIntegerLiteral() ast.Expression { + lit := &ast.IntegerLiteral{Token: p.curToken} + + value, err := strconv.ParseInt(p.curToken.Literal, 0, 64) + if err != nil { + msg := fmt.Sprintf("could not parse %q as integer", p.curToken.Literal) + p.errors = append(p.errors, msg) + return nil + } + + lit.Value = value + + return lit +} + +func (p *Parser) parseStringLiteral() ast.Expression { + return &ast.StringLiteral{Token: p.curToken, Value: p.curToken.Literal} +} + +func (p *Parser) parsePrefixExpression() ast.Expression { + expression := &ast.PrefixExpression{ + Token: p.curToken, + Operator: p.curToken.Literal, + } + + p.nextToken() + + expression.Right = p.parseExpression(PREFIX) + + return expression +} + +func (p *Parser) parseInfixExpression(left ast.Expression) ast.Expression { + expression := &ast.InfixExpression{ + Token: p.curToken, + Operator: p.curToken.Literal, + Left: left, + } + + precedence := p.curPrecedence() + p.nextToken() + expression.Right = p.parseExpression(precedence) + + return expression +} + +func (p *Parser) parseBoolean() ast.Expression { + return &ast.Boolean{Token: p.curToken, Value: p.curTokenIs(token.TRUE)} +} + +func (p *Parser) parseGroupedExpression() ast.Expression { + p.nextToken() + + exp := p.parseExpression(LOWEST) + + if !p.expectPeek(token.RPAREN) { + return nil + } + + return exp +} + +func (p *Parser) parseIfExpression() ast.Expression { + expression := &ast.IfExpression{Token: p.curToken} + + if !p.expectPeek(token.LPAREN) { + return nil + } + + p.nextToken() + expression.Condition = p.parseExpression(LOWEST) + + if !p.expectPeek(token.RPAREN) { + return nil + } + + if !p.expectPeek(token.LBRACE) { + return nil + } + + expression.Consequence = p.parseBlockStatement() + + if p.peekTokenIs(token.ELSE) { + p.nextToken() + + if !p.expectPeek(token.LBRACE) { + return nil + } + + expression.Alternative = p.parseBlockStatement() + } + + return expression +} + +func (p *Parser) parseBlockStatement() *ast.BlockStatement { + block := &ast.BlockStatement{Token: p.curToken} + block.Statements = []ast.Statement{} + + p.nextToken() + + for !p.curTokenIs(token.RBRACE) && !p.curTokenIs(token.EOF) { + stmt := p.parseStatement() + if stmt != nil { + block.Statements = append(block.Statements, stmt) + } + p.nextToken() + } + + return block +} + +func (p *Parser) parseFunctionLiteral() ast.Expression { + lit := &ast.FunctionLiteral{Token: p.curToken} + + if !p.expectPeek(token.LPAREN) { + return nil + } + + lit.Parameters = p.parseFunctionParameters() + + if !p.expectPeek(token.LBRACE) { + return nil + } + + lit.Body = p.parseBlockStatement() + + return lit +} + +func (p *Parser) parseFunctionParameters() []*ast.Identifier { + identifiers := []*ast.Identifier{} + + if p.peekTokenIs(token.RPAREN) { + p.nextToken() + return identifiers + } + + p.nextToken() + + ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + identifiers = append(identifiers, ident) + + for p.peekTokenIs(token.COMMA) { + p.nextToken() + p.nextToken() + ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + identifiers = append(identifiers, ident) + } + + if !p.expectPeek(token.RPAREN) { + return nil + } + + return identifiers +} + +func (p *Parser) parseCallExpression(function ast.Expression) ast.Expression { + exp := &ast.CallExpression{Token: p.curToken, Function: function} + exp.Arguments = p.parseExpressionList(token.RPAREN) + return exp +} + +func (p *Parser) parseExpressionList(end token.TokenType) []ast.Expression { + list := []ast.Expression{} + + if p.peekTokenIs(end) { + p.nextToken() + return list + } + + p.nextToken() + list = append(list, p.parseExpression(LOWEST)) + + for p.peekTokenIs(token.COMMA) { + p.nextToken() + p.nextToken() + list = append(list, p.parseExpression(LOWEST)) + } + + if !p.expectPeek(end) { + return nil + } + + return list +} + +func (p *Parser) parseArrayLiteral() ast.Expression { + array := &ast.ArrayLiteral{Token: p.curToken} + + array.Elements = p.parseExpressionList(token.RBRACKET) + + return array +} + +func (p *Parser) parseIndexExpression(left ast.Expression) ast.Expression { + exp := &ast.IndexExpression{Token: p.curToken, Left: left} + + p.nextToken() + exp.Index = p.parseExpression(LOWEST) + + if !p.expectPeek(token.RBRACKET) { + return nil + } + + return exp +} + +func (p *Parser) parseHashLiteral() ast.Expression { + hash := &ast.HashLiteral{Token: p.curToken} + hash.Pairs = make(map[ast.Expression]ast.Expression) + + for !p.peekTokenIs(token.RBRACE) { + p.nextToken() + key := p.parseExpression(LOWEST) + + if !p.expectPeek(token.COLON) { + return nil + } + + p.nextToken() + value := p.parseExpression(LOWEST) + + hash.Pairs[key] = value + + if !p.peekTokenIs(token.RBRACE) && !p.expectPeek(token.COMMA) { + return nil + } + } + + if !p.expectPeek(token.RBRACE) { + return nil + } + + return hash +} + +func (p *Parser) registerPrefix(tokenType token.TokenType, fn prefixParseFn) { + p.prefixParseFns[tokenType] = fn +} + +func (p *Parser) registerInfix(tokenType token.TokenType, fn infixParseFn) { + p.infixParseFns[tokenType] = fn +} diff --git a/wcig_code_1_2/05/src/monkey/parser/parser_test.go b/wcig_code_1_2/05/src/monkey/parser/parser_test.go new file mode 100644 index 0000000..674487d --- /dev/null +++ b/wcig_code_1_2/05/src/monkey/parser/parser_test.go @@ -0,0 +1,1083 @@ +package parser + +import ( + "fmt" + "monkey/ast" + "monkey/lexer" + "testing" +) + +func TestLetStatements(t *testing.T) { + tests := []struct { + input string + expectedIdentifier string + expectedValue interface{} + }{ + {"let x = 5;", "x", 5}, + {"let y = true;", "y", true}, + {"let foobar = y;", "foobar", "y"}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain 1 statements. got=%d", + len(program.Statements)) + } + + stmt := program.Statements[0] + if !testLetStatement(t, stmt, tt.expectedIdentifier) { + return + } + + val := stmt.(*ast.LetStatement).Value + if !testLiteralExpression(t, val, tt.expectedValue) { + return + } + } +} + +func TestReturnStatements(t *testing.T) { + tests := []struct { + input string + expectedValue interface{} + }{ + {"return 5;", 5}, + {"return true;", true}, + {"return foobar;", "foobar"}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain 1 statements. got=%d", + len(program.Statements)) + } + + stmt := program.Statements[0] + returnStmt, ok := stmt.(*ast.ReturnStatement) + if !ok { + t.Fatalf("stmt not *ast.returnStatement. got=%T", stmt) + } + if returnStmt.TokenLiteral() != "return" { + t.Fatalf("returnStmt.TokenLiteral not 'return', got %q", + returnStmt.TokenLiteral()) + } + if testLiteralExpression(t, returnStmt.ReturnValue, tt.expectedValue) { + return + } + } +} + +func TestIdentifierExpression(t *testing.T) { + input := "foobar;" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program has not enough statements. got=%d", + len(program.Statements)) + } + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + ident, ok := stmt.Expression.(*ast.Identifier) + if !ok { + t.Fatalf("exp not *ast.Identifier. got=%T", stmt.Expression) + } + if ident.Value != "foobar" { + t.Errorf("ident.Value not %s. got=%s", "foobar", ident.Value) + } + if ident.TokenLiteral() != "foobar" { + t.Errorf("ident.TokenLiteral not %s. got=%s", "foobar", + ident.TokenLiteral()) + } +} + +func TestIntegerLiteralExpression(t *testing.T) { + input := "5;" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program has not enough statements. got=%d", + len(program.Statements)) + } + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + literal, ok := stmt.Expression.(*ast.IntegerLiteral) + if !ok { + t.Fatalf("exp not *ast.IntegerLiteral. got=%T", stmt.Expression) + } + if literal.Value != 5 { + t.Errorf("literal.Value not %d. got=%d", 5, literal.Value) + } + if literal.TokenLiteral() != "5" { + t.Errorf("literal.TokenLiteral not %s. got=%s", "5", + literal.TokenLiteral()) + } +} + +func TestParsingPrefixExpressions(t *testing.T) { + prefixTests := []struct { + input string + operator string + value interface{} + }{ + {"!5;", "!", 5}, + {"-15;", "-", 15}, + {"!foobar;", "!", "foobar"}, + {"-foobar;", "-", "foobar"}, + {"!true;", "!", true}, + {"!false;", "!", false}, + } + + for _, tt := range prefixTests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.PrefixExpression) + if !ok { + t.Fatalf("stmt is not ast.PrefixExpression. got=%T", stmt.Expression) + } + if exp.Operator != tt.operator { + t.Fatalf("exp.Operator is not '%s'. got=%s", + tt.operator, exp.Operator) + } + if !testLiteralExpression(t, exp.Right, tt.value) { + return + } + } +} + +func TestParsingInfixExpressions(t *testing.T) { + infixTests := []struct { + input string + leftValue interface{} + operator string + rightValue interface{} + }{ + {"5 + 5;", 5, "+", 5}, + {"5 - 5;", 5, "-", 5}, + {"5 * 5;", 5, "*", 5}, + {"5 / 5;", 5, "/", 5}, + {"5 > 5;", 5, ">", 5}, + {"5 < 5;", 5, "<", 5}, + {"5 == 5;", 5, "==", 5}, + {"5 != 5;", 5, "!=", 5}, + {"foobar + barfoo;", "foobar", "+", "barfoo"}, + {"foobar - barfoo;", "foobar", "-", "barfoo"}, + {"foobar * barfoo;", "foobar", "*", "barfoo"}, + {"foobar / barfoo;", "foobar", "/", "barfoo"}, + {"foobar > barfoo;", "foobar", ">", "barfoo"}, + {"foobar < barfoo;", "foobar", "<", "barfoo"}, + {"foobar == barfoo;", "foobar", "==", "barfoo"}, + {"foobar != barfoo;", "foobar", "!=", "barfoo"}, + {"true == true", true, "==", true}, + {"true != false", true, "!=", false}, + {"false == false", false, "==", false}, + } + + for _, tt := range infixTests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + if !testInfixExpression(t, stmt.Expression, tt.leftValue, + tt.operator, tt.rightValue) { + return + } + } +} + +func TestOperatorPrecedenceParsing(t *testing.T) { + tests := []struct { + input string + expected string + }{ + { + "-a * b", + "((-a) * b)", + }, + { + "!-a", + "(!(-a))", + }, + { + "a + b + c", + "((a + b) + c)", + }, + { + "a + b - c", + "((a + b) - c)", + }, + { + "a * b * c", + "((a * b) * c)", + }, + { + "a * b / c", + "((a * b) / c)", + }, + { + "a + b / c", + "(a + (b / c))", + }, + { + "a + b * c + d / e - f", + "(((a + (b * c)) + (d / e)) - f)", + }, + { + "3 + 4; -5 * 5", + "(3 + 4)((-5) * 5)", + }, + { + "5 > 4 == 3 < 4", + "((5 > 4) == (3 < 4))", + }, + { + "5 < 4 != 3 > 4", + "((5 < 4) != (3 > 4))", + }, + { + "3 + 4 * 5 == 3 * 1 + 4 * 5", + "((3 + (4 * 5)) == ((3 * 1) + (4 * 5)))", + }, + { + "true", + "true", + }, + { + "false", + "false", + }, + { + "3 > 5 == false", + "((3 > 5) == false)", + }, + { + "3 < 5 == true", + "((3 < 5) == true)", + }, + { + "1 + (2 + 3) + 4", + "((1 + (2 + 3)) + 4)", + }, + { + "(5 + 5) * 2", + "((5 + 5) * 2)", + }, + { + "2 / (5 + 5)", + "(2 / (5 + 5))", + }, + { + "(5 + 5) * 2 * (5 + 5)", + "(((5 + 5) * 2) * (5 + 5))", + }, + { + "-(5 + 5)", + "(-(5 + 5))", + }, + { + "!(true == true)", + "(!(true == true))", + }, + { + "a + add(b * c) + d", + "((a + add((b * c))) + d)", + }, + { + "add(a, b, 1, 2 * 3, 4 + 5, add(6, 7 * 8))", + "add(a, b, 1, (2 * 3), (4 + 5), add(6, (7 * 8)))", + }, + { + "add(a + b + c * d / f + g)", + "add((((a + b) + ((c * d) / f)) + g))", + }, + { + "a * [1, 2, 3, 4][b * c] * d", + "((a * ([1, 2, 3, 4][(b * c)])) * d)", + }, + { + "add(a * b[2], b[1], 2 * [1, 2][1])", + "add((a * (b[2])), (b[1]), (2 * ([1, 2][1])))", + }, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + actual := program.String() + if actual != tt.expected { + t.Errorf("expected=%q, got=%q", tt.expected, actual) + } + } +} + +func TestBooleanExpression(t *testing.T) { + tests := []struct { + input string + expectedBoolean bool + }{ + {"true;", true}, + {"false;", false}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program has not enough statements. got=%d", + len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + boolean, ok := stmt.Expression.(*ast.Boolean) + if !ok { + t.Fatalf("exp not *ast.Boolean. got=%T", stmt.Expression) + } + if boolean.Value != tt.expectedBoolean { + t.Errorf("boolean.Value not %t. got=%t", tt.expectedBoolean, + boolean.Value) + } + } +} + +func TestIfExpression(t *testing.T) { + input := `if (x < y) { x }` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.IfExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.IfExpression. got=%T", + stmt.Expression) + } + + if !testInfixExpression(t, exp.Condition, "x", "<", "y") { + return + } + + if len(exp.Consequence.Statements) != 1 { + t.Errorf("consequence is not 1 statements. got=%d\n", + len(exp.Consequence.Statements)) + } + + consequence, ok := exp.Consequence.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", + exp.Consequence.Statements[0]) + } + + if !testIdentifier(t, consequence.Expression, "x") { + return + } + + if exp.Alternative != nil { + t.Errorf("exp.Alternative.Statements was not nil. got=%+v", exp.Alternative) + } +} + +func TestIfElseExpression(t *testing.T) { + input := `if (x < y) { x } else { y }` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.IfExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.IfExpression. got=%T", stmt.Expression) + } + + if !testInfixExpression(t, exp.Condition, "x", "<", "y") { + return + } + + if len(exp.Consequence.Statements) != 1 { + t.Errorf("consequence is not 1 statements. got=%d\n", + len(exp.Consequence.Statements)) + } + + consequence, ok := exp.Consequence.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", + exp.Consequence.Statements[0]) + } + + if !testIdentifier(t, consequence.Expression, "x") { + return + } + + if len(exp.Alternative.Statements) != 1 { + t.Errorf("exp.Alternative.Statements does not contain 1 statements. got=%d\n", + len(exp.Alternative.Statements)) + } + + alternative, ok := exp.Alternative.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", + exp.Alternative.Statements[0]) + } + + if !testIdentifier(t, alternative.Expression, "y") { + return + } +} + +func TestFunctionLiteralParsing(t *testing.T) { + input := `fn(x, y) { x + y; }` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + function, ok := stmt.Expression.(*ast.FunctionLiteral) + if !ok { + t.Fatalf("stmt.Expression is not ast.FunctionLiteral. got=%T", + stmt.Expression) + } + + if len(function.Parameters) != 2 { + t.Fatalf("function literal parameters wrong. want 2, got=%d\n", + len(function.Parameters)) + } + + testLiteralExpression(t, function.Parameters[0], "x") + testLiteralExpression(t, function.Parameters[1], "y") + + if len(function.Body.Statements) != 1 { + t.Fatalf("function.Body.Statements has not 1 statements. got=%d\n", + len(function.Body.Statements)) + } + + bodyStmt, ok := function.Body.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("function body stmt is not ast.ExpressionStatement. got=%T", + function.Body.Statements[0]) + } + + testInfixExpression(t, bodyStmt.Expression, "x", "+", "y") +} + +func TestFunctionParameterParsing(t *testing.T) { + tests := []struct { + input string + expectedParams []string + }{ + {input: "fn() {};", expectedParams: []string{}}, + {input: "fn(x) {};", expectedParams: []string{"x"}}, + {input: "fn(x, y, z) {};", expectedParams: []string{"x", "y", "z"}}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + function := stmt.Expression.(*ast.FunctionLiteral) + + if len(function.Parameters) != len(tt.expectedParams) { + t.Errorf("length parameters wrong. want %d, got=%d\n", + len(tt.expectedParams), len(function.Parameters)) + } + + for i, ident := range tt.expectedParams { + testLiteralExpression(t, function.Parameters[i], ident) + } + } +} + +func TestCallExpressionParsing(t *testing.T) { + input := "add(1, 2 * 3, 4 + 5);" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("stmt is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.CallExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.CallExpression. got=%T", + stmt.Expression) + } + + if !testIdentifier(t, exp.Function, "add") { + return + } + + if len(exp.Arguments) != 3 { + t.Fatalf("wrong length of arguments. got=%d", len(exp.Arguments)) + } + + testLiteralExpression(t, exp.Arguments[0], 1) + testInfixExpression(t, exp.Arguments[1], 2, "*", 3) + testInfixExpression(t, exp.Arguments[2], 4, "+", 5) +} + +func TestCallExpressionParameterParsing(t *testing.T) { + tests := []struct { + input string + expectedIdent string + expectedArgs []string + }{ + { + input: "add();", + expectedIdent: "add", + expectedArgs: []string{}, + }, + { + input: "add(1);", + expectedIdent: "add", + expectedArgs: []string{"1"}, + }, + { + input: "add(1, 2 * 3, 4 + 5);", + expectedIdent: "add", + expectedArgs: []string{"1", "(2 * 3)", "(4 + 5)"}, + }, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + exp, ok := stmt.Expression.(*ast.CallExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.CallExpression. got=%T", + stmt.Expression) + } + + if !testIdentifier(t, exp.Function, tt.expectedIdent) { + return + } + + if len(exp.Arguments) != len(tt.expectedArgs) { + t.Fatalf("wrong number of arguments. want=%d, got=%d", + len(tt.expectedArgs), len(exp.Arguments)) + } + + for i, arg := range tt.expectedArgs { + if exp.Arguments[i].String() != arg { + t.Errorf("argument %d wrong. want=%q, got=%q", i, + arg, exp.Arguments[i].String()) + } + } + } +} + +func TestStringLiteralExpression(t *testing.T) { + input := `"hello world";` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + literal, ok := stmt.Expression.(*ast.StringLiteral) + if !ok { + t.Fatalf("exp not *ast.StringLiteral. got=%T", stmt.Expression) + } + + if literal.Value != "hello world" { + t.Errorf("literal.Value not %q. got=%q", "hello world", literal.Value) + } +} + +func TestParsingEmptyArrayLiterals(t *testing.T) { + input := "[]" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + array, ok := stmt.Expression.(*ast.ArrayLiteral) + if !ok { + t.Fatalf("exp not ast.ArrayLiteral. got=%T", stmt.Expression) + } + + if len(array.Elements) != 0 { + t.Errorf("len(array.Elements) not 0. got=%d", len(array.Elements)) + } +} + +func TestParsingArrayLiterals(t *testing.T) { + input := "[1, 2 * 2, 3 + 3]" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + array, ok := stmt.Expression.(*ast.ArrayLiteral) + if !ok { + t.Fatalf("exp not ast.ArrayLiteral. got=%T", stmt.Expression) + } + + if len(array.Elements) != 3 { + t.Fatalf("len(array.Elements) not 3. got=%d", len(array.Elements)) + } + + testIntegerLiteral(t, array.Elements[0], 1) + testInfixExpression(t, array.Elements[1], 2, "*", 2) + testInfixExpression(t, array.Elements[2], 3, "+", 3) +} + +func TestParsingIndexExpressions(t *testing.T) { + input := "myArray[1 + 1]" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + indexExp, ok := stmt.Expression.(*ast.IndexExpression) + if !ok { + t.Fatalf("exp not *ast.IndexExpression. got=%T", stmt.Expression) + } + + if !testIdentifier(t, indexExp.Left, "myArray") { + return + } + + if !testInfixExpression(t, indexExp.Index, 1, "+", 1) { + return + } +} + +func TestParsingEmptyHashLiteral(t *testing.T) { + input := "{}" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + if len(hash.Pairs) != 0 { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } +} + +func TestParsingHashLiteralsStringKeys(t *testing.T) { + input := `{"one": 1, "two": 2, "three": 3}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + expected := map[string]int64{ + "one": 1, + "two": 2, + "three": 3, + } + + if len(hash.Pairs) != len(expected) { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } + + for key, value := range hash.Pairs { + literal, ok := key.(*ast.StringLiteral) + if !ok { + t.Errorf("key is not ast.StringLiteral. got=%T", key) + continue + } + + expectedValue := expected[literal.String()] + testIntegerLiteral(t, value, expectedValue) + } +} + +func TestParsingHashLiteralsBooleanKeys(t *testing.T) { + input := `{true: 1, false: 2}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + expected := map[string]int64{ + "true": 1, + "false": 2, + } + + if len(hash.Pairs) != len(expected) { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } + + for key, value := range hash.Pairs { + boolean, ok := key.(*ast.Boolean) + if !ok { + t.Errorf("key is not ast.BooleanLiteral. got=%T", key) + continue + } + + expectedValue := expected[boolean.String()] + testIntegerLiteral(t, value, expectedValue) + } +} + +func TestParsingHashLiteralsIntegerKeys(t *testing.T) { + input := `{1: 1, 2: 2, 3: 3}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + expected := map[string]int64{ + "1": 1, + "2": 2, + "3": 3, + } + + if len(hash.Pairs) != len(expected) { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } + + for key, value := range hash.Pairs { + integer, ok := key.(*ast.IntegerLiteral) + if !ok { + t.Errorf("key is not ast.IntegerLiteral. got=%T", key) + continue + } + + expectedValue := expected[integer.String()] + + testIntegerLiteral(t, value, expectedValue) + } +} + +func TestParsingHashLiteralsWithExpressions(t *testing.T) { + input := `{"one": 0 + 1, "two": 10 - 8, "three": 15 / 5}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + if len(hash.Pairs) != 3 { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } + + tests := map[string]func(ast.Expression){ + "one": func(e ast.Expression) { + testInfixExpression(t, e, 0, "+", 1) + }, + "two": func(e ast.Expression) { + testInfixExpression(t, e, 10, "-", 8) + }, + "three": func(e ast.Expression) { + testInfixExpression(t, e, 15, "/", 5) + }, + } + + for key, value := range hash.Pairs { + literal, ok := key.(*ast.StringLiteral) + if !ok { + t.Errorf("key is not ast.StringLiteral. got=%T", key) + continue + } + + testFunc, ok := tests[literal.String()] + if !ok { + t.Errorf("No test function for key %q found", literal.String()) + continue + } + + testFunc(value) + } +} + +func testLetStatement(t *testing.T, s ast.Statement, name string) bool { + if s.TokenLiteral() != "let" { + t.Errorf("s.TokenLiteral not 'let'. got=%q", s.TokenLiteral()) + return false + } + + letStmt, ok := s.(*ast.LetStatement) + if !ok { + t.Errorf("s not *ast.LetStatement. got=%T", s) + return false + } + + if letStmt.Name.Value != name { + t.Errorf("letStmt.Name.Value not '%s'. got=%s", name, letStmt.Name.Value) + return false + } + + if letStmt.Name.TokenLiteral() != name { + t.Errorf("letStmt.Name.TokenLiteral() not '%s'. got=%s", + name, letStmt.Name.TokenLiteral()) + return false + } + + return true +} + +func testInfixExpression(t *testing.T, exp ast.Expression, left interface{}, + operator string, right interface{}) bool { + + opExp, ok := exp.(*ast.InfixExpression) + if !ok { + t.Errorf("exp is not ast.InfixExpression. got=%T(%s)", exp, exp) + return false + } + + if !testLiteralExpression(t, opExp.Left, left) { + return false + } + + if opExp.Operator != operator { + t.Errorf("exp.Operator is not '%s'. got=%q", operator, opExp.Operator) + return false + } + + if !testLiteralExpression(t, opExp.Right, right) { + return false + } + + return true +} + +func testLiteralExpression( + t *testing.T, + exp ast.Expression, + expected interface{}, +) bool { + switch v := expected.(type) { + case int: + return testIntegerLiteral(t, exp, int64(v)) + case int64: + return testIntegerLiteral(t, exp, v) + case string: + return testIdentifier(t, exp, v) + case bool: + return testBooleanLiteral(t, exp, v) + } + t.Errorf("type of exp not handled. got=%T", exp) + return false +} + +func testIntegerLiteral(t *testing.T, il ast.Expression, value int64) bool { + integ, ok := il.(*ast.IntegerLiteral) + if !ok { + t.Errorf("il not *ast.IntegerLiteral. got=%T", il) + return false + } + + if integ.Value != value { + t.Errorf("integ.Value not %d. got=%d", value, integ.Value) + return false + } + + if integ.TokenLiteral() != fmt.Sprintf("%d", value) { + t.Errorf("integ.TokenLiteral not %d. got=%s", value, + integ.TokenLiteral()) + return false + } + + return true +} + +func testIdentifier(t *testing.T, exp ast.Expression, value string) bool { + ident, ok := exp.(*ast.Identifier) + if !ok { + t.Errorf("exp not *ast.Identifier. got=%T", exp) + return false + } + + if ident.Value != value { + t.Errorf("ident.Value not %s. got=%s", value, ident.Value) + return false + } + + if ident.TokenLiteral() != value { + t.Errorf("ident.TokenLiteral not %s. got=%s", value, + ident.TokenLiteral()) + return false + } + + return true +} + +func testBooleanLiteral(t *testing.T, exp ast.Expression, value bool) bool { + bo, ok := exp.(*ast.Boolean) + if !ok { + t.Errorf("exp not *ast.Boolean. got=%T", exp) + return false + } + + if bo.Value != value { + t.Errorf("bo.Value not %t. got=%t", value, bo.Value) + return false + } + + if bo.TokenLiteral() != fmt.Sprintf("%t", value) { + t.Errorf("bo.TokenLiteral not %t. got=%s", + value, bo.TokenLiteral()) + return false + } + + return true +} + +func checkParserErrors(t *testing.T, p *Parser) { + errors := p.Errors() + if len(errors) == 0 { + return + } + + t.Errorf("parser has %d errors", len(errors)) + for _, msg := range errors { + t.Errorf("parser error: %q", msg) + } + t.FailNow() +} diff --git a/wcig_code_1_2/05/src/monkey/parser/parser_tracing.go b/wcig_code_1_2/05/src/monkey/parser/parser_tracing.go new file mode 100644 index 0000000..5fc569b --- /dev/null +++ b/wcig_code_1_2/05/src/monkey/parser/parser_tracing.go @@ -0,0 +1,32 @@ +package parser + +import ( + "fmt" + "strings" +) + +var traceLevel int = 0 + +const traceIdentPlaceholder string = "\t" + +func identLevel() string { + return strings.Repeat(traceIdentPlaceholder, traceLevel-1) +} + +func tracePrint(fs string) { + fmt.Printf("%s%s\n", identLevel(), fs) +} + +func incIdent() { traceLevel = traceLevel + 1 } +func decIdent() { traceLevel = traceLevel - 1 } + +func trace(msg string) string { + incIdent() + tracePrint("BEGIN " + msg) + return msg +} + +func untrace(msg string) { + tracePrint("END " + msg) + decIdent() +} diff --git a/wcig_code_1_2/05/src/monkey/repl/repl.go b/wcig_code_1_2/05/src/monkey/repl/repl.go new file mode 100644 index 0000000..d14f36b --- /dev/null +++ b/wcig_code_1_2/05/src/monkey/repl/repl.go @@ -0,0 +1,83 @@ +package repl + +import ( + "bufio" + "fmt" + "io" + "monkey/compiler" + "monkey/lexer" + "monkey/object" + "monkey/parser" + "monkey/vm" +) + +const PROMPT = ">> " + +func Start(in io.Reader, out io.Writer) { + scanner := bufio.NewScanner(in) + + constants := []object.Object{} + globals := make([]object.Object, vm.GlobalsSize) + symbolTable := compiler.NewSymbolTable() + + for { + fmt.Fprintf(out, PROMPT) + scanned := scanner.Scan() + if !scanned { + return + } + + line := scanner.Text() + l := lexer.New(line) + p := parser.New(l) + + program := p.ParseProgram() + if len(p.Errors()) != 0 { + printParserErrors(out, p.Errors()) + continue + } + + comp := compiler.NewWithState(symbolTable, constants) + err := comp.Compile(program) + if err != nil { + fmt.Fprintf(out, "Woops! Compilation failed:\n %s\n", err) + continue + } + + code := comp.Bytecode() + constants = code.Constants + + machine := vm.NewWithGlobalsStore(code, globals) + err = machine.Run() + if err != nil { + fmt.Fprintf(out, "Woops! Executing bytecode failed:\n %s\n", err) + continue + } + + lastPopped := machine.LastPoppedStackElem() + io.WriteString(out, lastPopped.Inspect()) + io.WriteString(out, "\n") + } +} + +const MONKEY_FACE = ` __,__ + .--. .-" "-. .--. + / .. \/ .-. .-. \/ .. \ + | | '| / Y \ |' | | + | \ \ \ 0 | 0 / / / | + \ '- ,\.-"""""""-./, -' / + ''-' /_ ^ ^ _\ '-'' + | \._ _./ | + \ \ '~' / / + '._ '-=-' _.' + '-----' +` + +func printParserErrors(out io.Writer, errors []string) { + io.WriteString(out, MONKEY_FACE) + io.WriteString(out, "Woops! We ran into some monkey business here!\n") + io.WriteString(out, " parser errors:\n") + for _, msg := range errors { + io.WriteString(out, "\t"+msg+"\n") + } +} diff --git a/wcig_code_1_2/05/src/monkey/token/token.go b/wcig_code_1_2/05/src/monkey/token/token.go new file mode 100644 index 0000000..3d2d2f7 --- /dev/null +++ b/wcig_code_1_2/05/src/monkey/token/token.go @@ -0,0 +1,70 @@ +package token + +type TokenType string + +const ( + ILLEGAL = "ILLEGAL" + EOF = "EOF" + + // Identifiers + literals + IDENT = "IDENT" // add, foobar, x, y, ... + INT = "INT" // 1343456 + STRING = "STRING" // "foobar" + + // Operators + ASSIGN = "=" + PLUS = "+" + MINUS = "-" + BANG = "!" + ASTERISK = "*" + SLASH = "/" + + LT = "<" + GT = ">" + + EQ = "==" + NOT_EQ = "!=" + + // Delimiters + COMMA = "," + SEMICOLON = ";" + COLON = ":" + + LPAREN = "(" + RPAREN = ")" + LBRACE = "{" + RBRACE = "}" + LBRACKET = "[" + RBRACKET = "]" + + // Keywords + FUNCTION = "FUNCTION" + LET = "LET" + TRUE = "TRUE" + FALSE = "FALSE" + IF = "IF" + ELSE = "ELSE" + RETURN = "RETURN" +) + +type Token struct { + Type TokenType + Literal string +} + +var keywords = map[string]TokenType{ + "fn": FUNCTION, + "let": LET, + "true": TRUE, + "false": FALSE, + "if": IF, + "else": ELSE, + "return": RETURN, +} + +func LookupIdent(ident string) TokenType { + if tok, ok := keywords[ident]; ok { + return tok + } + return IDENT +} diff --git a/wcig_code_1_2/05/src/monkey/vm/vm.go b/wcig_code_1_2/05/src/monkey/vm/vm.go new file mode 100644 index 0000000..f8a2d1d --- /dev/null +++ b/wcig_code_1_2/05/src/monkey/vm/vm.go @@ -0,0 +1,281 @@ +package vm + +import ( + "fmt" + "monkey/code" + "monkey/compiler" + "monkey/object" +) + +const StackSize = 2048 +const GlobalsSize = 65536 + +var True = &object.Boolean{Value: true} +var False = &object.Boolean{Value: false} +var Null = &object.Null{} + +type VM struct { + constants []object.Object + instructions code.Instructions + + stack []object.Object + sp int // Always points to the next value. Top of stack is stack[sp-1] + + globals []object.Object +} + +func New(bytecode *compiler.Bytecode) *VM { + return &VM{ + instructions: bytecode.Instructions, + constants: bytecode.Constants, + + stack: make([]object.Object, StackSize), + sp: 0, + + globals: make([]object.Object, GlobalsSize), + } +} + +func NewWithGlobalsStore(bytecode *compiler.Bytecode, s []object.Object) *VM { + vm := New(bytecode) + vm.globals = s + return vm +} + +func (vm *VM) LastPoppedStackElem() object.Object { + return vm.stack[vm.sp] +} + +func (vm *VM) Run() error { + for ip := 0; ip < len(vm.instructions); ip++ { + op := code.Opcode(vm.instructions[ip]) + + switch op { + case code.OpConstant: + constIndex := code.ReadUint16(vm.instructions[ip+1:]) + ip += 2 + + err := vm.push(vm.constants[constIndex]) + if err != nil { + return err + } + + case code.OpPop: + vm.pop() + + case code.OpAdd, code.OpSub, code.OpMul, code.OpDiv: + err := vm.executeBinaryOperation(op) + if err != nil { + return err + } + + case code.OpTrue: + err := vm.push(True) + if err != nil { + return err + } + + case code.OpFalse: + err := vm.push(False) + if err != nil { + return err + } + + case code.OpEqual, code.OpNotEqual, code.OpGreaterThan: + err := vm.executeComparison(op) + if err != nil { + return err + } + + case code.OpBang: + err := vm.executeBangOperator() + if err != nil { + return err + } + + case code.OpMinus: + err := vm.executeMinusOperator() + if err != nil { + return err + } + + case code.OpJump: + pos := int(code.ReadUint16(vm.instructions[ip+1:])) + ip = pos - 1 + + case code.OpJumpNotTruthy: + pos := int(code.ReadUint16(vm.instructions[ip+1:])) + ip += 2 + + condition := vm.pop() + if !isTruthy(condition) { + ip = pos - 1 + } + + case code.OpNull: + err := vm.push(Null) + if err != nil { + return err + } + + case code.OpSetGlobal: + globalIndex := code.ReadUint16(vm.instructions[ip+1:]) + ip += 2 + + vm.globals[globalIndex] = vm.pop() + + case code.OpGetGlobal: + globalIndex := code.ReadUint16(vm.instructions[ip+1:]) + ip += 2 + + err := vm.push(vm.globals[globalIndex]) + if err != nil { + return err + } + } + } + + return nil +} + +func (vm *VM) push(o object.Object) error { + if vm.sp >= StackSize { + return fmt.Errorf("stack overflow") + } + + vm.stack[vm.sp] = o + vm.sp++ + + return nil +} + +func (vm *VM) pop() object.Object { + o := vm.stack[vm.sp-1] + vm.sp-- + return o +} + +func (vm *VM) executeBinaryOperation(op code.Opcode) error { + right := vm.pop() + left := vm.pop() + + leftType := left.Type() + rightType := right.Type() + + if leftType == object.INTEGER_OBJ && rightType == object.INTEGER_OBJ { + return vm.executeBinaryIntegerOperation(op, left, right) + } + + return fmt.Errorf("unsupported types for binary operation: %s %s", + leftType, rightType) +} + +func (vm *VM) executeBinaryIntegerOperation( + op code.Opcode, + left, right object.Object, +) error { + leftValue := left.(*object.Integer).Value + rightValue := right.(*object.Integer).Value + + var result int64 + + switch op { + case code.OpAdd: + result = leftValue + rightValue + case code.OpSub: + result = leftValue - rightValue + case code.OpMul: + result = leftValue * rightValue + case code.OpDiv: + result = leftValue / rightValue + default: + return fmt.Errorf("unknown integer operator: %d", op) + } + + return vm.push(&object.Integer{Value: result}) +} + +func (vm *VM) executeComparison(op code.Opcode) error { + right := vm.pop() + left := vm.pop() + + if left.Type() == object.INTEGER_OBJ && right.Type() == object.INTEGER_OBJ { + return vm.executeIntegerComparison(op, left, right) + } + + switch op { + case code.OpEqual: + return vm.push(nativeBoolToBooleanObject(right == left)) + case code.OpNotEqual: + return vm.push(nativeBoolToBooleanObject(right != left)) + default: + return fmt.Errorf("unknown operator: %d (%s %s)", + op, left.Type(), right.Type()) + } +} + +func (vm *VM) executeIntegerComparison( + op code.Opcode, + left, right object.Object, +) error { + leftValue := left.(*object.Integer).Value + rightValue := right.(*object.Integer).Value + + switch op { + case code.OpEqual: + return vm.push(nativeBoolToBooleanObject(rightValue == leftValue)) + case code.OpNotEqual: + return vm.push(nativeBoolToBooleanObject(rightValue != leftValue)) + case code.OpGreaterThan: + return vm.push(nativeBoolToBooleanObject(leftValue > rightValue)) + default: + return fmt.Errorf("unknown operator: %d", op) + } +} + +func (vm *VM) executeBangOperator() error { + operand := vm.pop() + + switch operand { + case True: + return vm.push(False) + case False: + return vm.push(True) + case Null: + return vm.push(True) + default: + return vm.push(False) + } +} + +func (vm *VM) executeMinusOperator() error { + operand := vm.pop() + + if operand.Type() != object.INTEGER_OBJ { + return fmt.Errorf("unsupported type for negation: %s", operand.Type()) + } + + value := operand.(*object.Integer).Value + return vm.push(&object.Integer{Value: -value}) +} + +func nativeBoolToBooleanObject(input bool) *object.Boolean { + if input { + return True + } + return False +} + +func isTruthy(obj object.Object) bool { + switch obj := obj.(type) { + + case *object.Boolean: + return obj.Value + + case *object.Null: + return false + + default: + return true + } +} diff --git a/wcig_code_1_2/05/src/monkey/vm/vm_test.go b/wcig_code_1_2/05/src/monkey/vm/vm_test.go new file mode 100644 index 0000000..efc5e81 --- /dev/null +++ b/wcig_code_1_2/05/src/monkey/vm/vm_test.go @@ -0,0 +1,187 @@ +package vm + +import ( + "fmt" + "monkey/ast" + "monkey/compiler" + "monkey/lexer" + "monkey/object" + "monkey/parser" + "testing" +) + +func TestIntegerArithmetic(t *testing.T) { + tests := []vmTestCase{ + {"1", 1}, + {"2", 2}, + {"1 + 2", 3}, + {"1 - 2", -1}, + {"1 * 2", 2}, + {"4 / 2", 2}, + {"50 / 2 * 2 + 10 - 5", 55}, + {"5 * (2 + 10)", 60}, + {"5 + 5 + 5 + 5 - 10", 10}, + {"2 * 2 * 2 * 2 * 2", 32}, + {"5 * 2 + 10", 20}, + {"5 + 2 * 10", 25}, + {"5 * (2 + 10)", 60}, + {"-5", -5}, + {"-10", -10}, + {"-50 + 100 + -50", 0}, + {"(5 + 10 * 2 + 15 / 3) * 2 + -10", 50}, + } + + runVmTests(t, tests) +} + +func TestBooleanExpressions(t *testing.T) { + tests := []vmTestCase{ + {"true", true}, + {"false", false}, + {"1 < 2", true}, + {"1 > 2", false}, + {"1 < 1", false}, + {"1 > 1", false}, + {"1 == 1", true}, + {"1 != 1", false}, + {"1 == 2", false}, + {"1 != 2", true}, + {"true == true", true}, + {"false == false", true}, + {"true == false", false}, + {"true != false", true}, + {"false != true", true}, + {"(1 < 2) == true", true}, + {"(1 < 2) == false", false}, + {"(1 > 2) == true", false}, + {"(1 > 2) == false", true}, + {"!true", false}, + {"!false", true}, + {"!5", false}, + {"!!true", true}, + {"!!false", false}, + {"!!5", true}, + {"!(if (false) { 5; })", true}, + } + + runVmTests(t, tests) +} + +func TestConditionals(t *testing.T) { + tests := []vmTestCase{ + {"if (true) { 10 }", 10}, + {"if (true) { 10 } else { 20 }", 10}, + {"if (false) { 10 } else { 20 } ", 20}, + {"if (1) { 10 }", 10}, + {"if (1 < 2) { 10 }", 10}, + {"if (1 < 2) { 10 } else { 20 }", 10}, + {"if (1 > 2) { 10 } else { 20 }", 20}, + {"if (1 > 2) { 10 }", Null}, + {"if (false) { 10 }", Null}, + {"if ((if (false) { 10 })) { 10 } else { 20 }", 20}, + } + + runVmTests(t, tests) +} + +func TestGlobalLetStatements(t *testing.T) { + tests := []vmTestCase{ + {"let one = 1; one", 1}, + {"let one = 1; let two = 2; one + two", 3}, + {"let one = 1; let two = one + one; one + two", 3}, + } + + runVmTests(t, tests) +} + +type vmTestCase struct { + input string + expected interface{} +} + +func runVmTests(t *testing.T, tests []vmTestCase) { + t.Helper() + + for _, tt := range tests { + program := parse(tt.input) + + comp := compiler.New() + err := comp.Compile(program) + if err != nil { + t.Fatalf("compiler error: %s", err) + } + + vm := New(comp.Bytecode()) + err = vm.Run() + if err != nil { + t.Fatalf("vm error: %s", err) + } + + stackElem := vm.LastPoppedStackElem() + + testExpectedObject(t, tt.expected, stackElem) + } +} + +func parse(input string) *ast.Program { + l := lexer.New(input) + p := parser.New(l) + return p.ParseProgram() +} + +func testExpectedObject( + t *testing.T, + expected interface{}, + actual object.Object, +) { + t.Helper() + + switch expected := expected.(type) { + case int: + err := testIntegerObject(int64(expected), actual) + if err != nil { + t.Errorf("testIntegerObject failed: %s", err) + } + + case bool: + err := testBooleanObject(bool(expected), actual) + if err != nil { + t.Errorf("testBooleanObject failed: %s", err) + } + + case *object.Null: + if actual != Null { + t.Errorf("object is not Null: %T (%+v)", actual, actual) + } + } +} + +func testIntegerObject(expected int64, actual object.Object) error { + result, ok := actual.(*object.Integer) + if !ok { + return fmt.Errorf("object is not Integer. got=%T (%+v)", + actual, actual) + } + + if result.Value != expected { + return fmt.Errorf("object has wrong value. got=%d, want=%d", + result.Value, expected) + } + + return nil +} + +func testBooleanObject(expected bool, actual object.Object) error { + result, ok := actual.(*object.Boolean) + if !ok { + return fmt.Errorf("object is not Boolean. got=%T (%+v)", + actual, actual) + } + + if result.Value != expected { + return fmt.Errorf("object has wrong value. got=%t, want=%t", + result.Value, expected) + } + + return nil +} diff --git a/wcig_code_1_2/06/.envrc b/wcig_code_1_2/06/.envrc new file mode 100644 index 0000000..fb65ef4 --- /dev/null +++ b/wcig_code_1_2/06/.envrc @@ -0,0 +1 @@ +export GOPATH=$(pwd) diff --git a/wcig_code_1_2/06/src/monkey/ast/ast.go b/wcig_code_1_2/06/src/monkey/ast/ast.go new file mode 100644 index 0000000..fb30b05 --- /dev/null +++ b/wcig_code_1_2/06/src/monkey/ast/ast.go @@ -0,0 +1,339 @@ +package ast + +import ( + "bytes" + "monkey/token" + "strings" +) + +// The base Node interface +type Node interface { + TokenLiteral() string + String() string +} + +// All statement nodes implement this +type Statement interface { + Node + statementNode() +} + +// All expression nodes implement this +type Expression interface { + Node + expressionNode() +} + +type Program struct { + Statements []Statement +} + +func (p *Program) TokenLiteral() string { + if len(p.Statements) > 0 { + return p.Statements[0].TokenLiteral() + } else { + return "" + } +} + +func (p *Program) String() string { + var out bytes.Buffer + + for _, s := range p.Statements { + out.WriteString(s.String()) + } + + return out.String() +} + +// Statements +type LetStatement struct { + Token token.Token // the token.LET token + Name *Identifier + Value Expression +} + +func (ls *LetStatement) statementNode() {} +func (ls *LetStatement) TokenLiteral() string { return ls.Token.Literal } +func (ls *LetStatement) String() string { + var out bytes.Buffer + + out.WriteString(ls.TokenLiteral() + " ") + out.WriteString(ls.Name.String()) + out.WriteString(" = ") + + if ls.Value != nil { + out.WriteString(ls.Value.String()) + } + + out.WriteString(";") + + return out.String() +} + +type ReturnStatement struct { + Token token.Token // the 'return' token + ReturnValue Expression +} + +func (rs *ReturnStatement) statementNode() {} +func (rs *ReturnStatement) TokenLiteral() string { return rs.Token.Literal } +func (rs *ReturnStatement) String() string { + var out bytes.Buffer + + out.WriteString(rs.TokenLiteral() + " ") + + if rs.ReturnValue != nil { + out.WriteString(rs.ReturnValue.String()) + } + + out.WriteString(";") + + return out.String() +} + +type ExpressionStatement struct { + Token token.Token // the first token of the expression + Expression Expression +} + +func (es *ExpressionStatement) statementNode() {} +func (es *ExpressionStatement) TokenLiteral() string { return es.Token.Literal } +func (es *ExpressionStatement) String() string { + if es.Expression != nil { + return es.Expression.String() + } + return "" +} + +type BlockStatement struct { + Token token.Token // the { token + Statements []Statement +} + +func (bs *BlockStatement) statementNode() {} +func (bs *BlockStatement) TokenLiteral() string { return bs.Token.Literal } +func (bs *BlockStatement) String() string { + var out bytes.Buffer + + for _, s := range bs.Statements { + out.WriteString(s.String()) + } + + return out.String() +} + +// Expressions +type Identifier struct { + Token token.Token // the token.IDENT token + Value string +} + +func (i *Identifier) expressionNode() {} +func (i *Identifier) TokenLiteral() string { return i.Token.Literal } +func (i *Identifier) String() string { return i.Value } + +type Boolean struct { + Token token.Token + Value bool +} + +func (b *Boolean) expressionNode() {} +func (b *Boolean) TokenLiteral() string { return b.Token.Literal } +func (b *Boolean) String() string { return b.Token.Literal } + +type IntegerLiteral struct { + Token token.Token + Value int64 +} + +func (il *IntegerLiteral) expressionNode() {} +func (il *IntegerLiteral) TokenLiteral() string { return il.Token.Literal } +func (il *IntegerLiteral) String() string { return il.Token.Literal } + +type PrefixExpression struct { + Token token.Token // The prefix token, e.g. ! + Operator string + Right Expression +} + +func (pe *PrefixExpression) expressionNode() {} +func (pe *PrefixExpression) TokenLiteral() string { return pe.Token.Literal } +func (pe *PrefixExpression) String() string { + var out bytes.Buffer + + out.WriteString("(") + out.WriteString(pe.Operator) + out.WriteString(pe.Right.String()) + out.WriteString(")") + + return out.String() +} + +type InfixExpression struct { + Token token.Token // The operator token, e.g. + + Left Expression + Operator string + Right Expression +} + +func (oe *InfixExpression) expressionNode() {} +func (oe *InfixExpression) TokenLiteral() string { return oe.Token.Literal } +func (oe *InfixExpression) String() string { + var out bytes.Buffer + + out.WriteString("(") + out.WriteString(oe.Left.String()) + out.WriteString(" " + oe.Operator + " ") + out.WriteString(oe.Right.String()) + out.WriteString(")") + + return out.String() +} + +type IfExpression struct { + Token token.Token // The 'if' token + Condition Expression + Consequence *BlockStatement + Alternative *BlockStatement +} + +func (ie *IfExpression) expressionNode() {} +func (ie *IfExpression) TokenLiteral() string { return ie.Token.Literal } +func (ie *IfExpression) String() string { + var out bytes.Buffer + + out.WriteString("if") + out.WriteString(ie.Condition.String()) + out.WriteString(" ") + out.WriteString(ie.Consequence.String()) + + if ie.Alternative != nil { + out.WriteString("else ") + out.WriteString(ie.Alternative.String()) + } + + return out.String() +} + +type FunctionLiteral struct { + Token token.Token // The 'fn' token + Parameters []*Identifier + Body *BlockStatement +} + +func (fl *FunctionLiteral) expressionNode() {} +func (fl *FunctionLiteral) TokenLiteral() string { return fl.Token.Literal } +func (fl *FunctionLiteral) String() string { + var out bytes.Buffer + + params := []string{} + for _, p := range fl.Parameters { + params = append(params, p.String()) + } + + out.WriteString(fl.TokenLiteral()) + out.WriteString("(") + out.WriteString(strings.Join(params, ", ")) + out.WriteString(") ") + out.WriteString(fl.Body.String()) + + return out.String() +} + +type CallExpression struct { + Token token.Token // The '(' token + Function Expression // Identifier or FunctionLiteral + Arguments []Expression +} + +func (ce *CallExpression) expressionNode() {} +func (ce *CallExpression) TokenLiteral() string { return ce.Token.Literal } +func (ce *CallExpression) String() string { + var out bytes.Buffer + + args := []string{} + for _, a := range ce.Arguments { + args = append(args, a.String()) + } + + out.WriteString(ce.Function.String()) + out.WriteString("(") + out.WriteString(strings.Join(args, ", ")) + out.WriteString(")") + + return out.String() +} + +type StringLiteral struct { + Token token.Token + Value string +} + +func (sl *StringLiteral) expressionNode() {} +func (sl *StringLiteral) TokenLiteral() string { return sl.Token.Literal } +func (sl *StringLiteral) String() string { return sl.Token.Literal } + +type ArrayLiteral struct { + Token token.Token // the '[' token + Elements []Expression +} + +func (al *ArrayLiteral) expressionNode() {} +func (al *ArrayLiteral) TokenLiteral() string { return al.Token.Literal } +func (al *ArrayLiteral) String() string { + var out bytes.Buffer + + elements := []string{} + for _, el := range al.Elements { + elements = append(elements, el.String()) + } + + out.WriteString("[") + out.WriteString(strings.Join(elements, ", ")) + out.WriteString("]") + + return out.String() +} + +type IndexExpression struct { + Token token.Token // The [ token + Left Expression + Index Expression +} + +func (ie *IndexExpression) expressionNode() {} +func (ie *IndexExpression) TokenLiteral() string { return ie.Token.Literal } +func (ie *IndexExpression) String() string { + var out bytes.Buffer + + out.WriteString("(") + out.WriteString(ie.Left.String()) + out.WriteString("[") + out.WriteString(ie.Index.String()) + out.WriteString("])") + + return out.String() +} + +type HashLiteral struct { + Token token.Token // the '{' token + Pairs map[Expression]Expression +} + +func (hl *HashLiteral) expressionNode() {} +func (hl *HashLiteral) TokenLiteral() string { return hl.Token.Literal } +func (hl *HashLiteral) String() string { + var out bytes.Buffer + + pairs := []string{} + for key, value := range hl.Pairs { + pairs = append(pairs, key.String()+":"+value.String()) + } + + out.WriteString("{") + out.WriteString(strings.Join(pairs, ", ")) + out.WriteString("}") + + return out.String() +} diff --git a/wcig_code_1_2/06/src/monkey/ast/ast_test.go b/wcig_code_1_2/06/src/monkey/ast/ast_test.go new file mode 100644 index 0000000..14a49dc --- /dev/null +++ b/wcig_code_1_2/06/src/monkey/ast/ast_test.go @@ -0,0 +1,28 @@ +package ast + +import ( + "monkey/token" + "testing" +) + +func TestString(t *testing.T) { + program := &Program{ + Statements: []Statement{ + &LetStatement{ + Token: token.Token{Type: token.LET, Literal: "let"}, + Name: &Identifier{ + Token: token.Token{Type: token.IDENT, Literal: "myVar"}, + Value: "myVar", + }, + Value: &Identifier{ + Token: token.Token{Type: token.IDENT, Literal: "anotherVar"}, + Value: "anotherVar", + }, + }, + }, + } + + if program.String() != "let myVar = anotherVar;" { + t.Errorf("program.String() wrong. got=%q", program.String()) + } +} diff --git a/wcig_code_1_2/06/src/monkey/code/code.go b/wcig_code_1_2/06/src/monkey/code/code.go new file mode 100644 index 0000000..ae40b79 --- /dev/null +++ b/wcig_code_1_2/06/src/monkey/code/code.go @@ -0,0 +1,179 @@ +package code + +import ( + "bytes" + "encoding/binary" + "fmt" +) + +type Instructions []byte + +func (ins Instructions) String() string { + var out bytes.Buffer + + i := 0 + for i < len(ins) { + def, err := Lookup(ins[i]) + if err != nil { + fmt.Fprintf(&out, "ERROR: %s\n", err) + continue + } + + operands, read := ReadOperands(def, ins[i+1:]) + + fmt.Fprintf(&out, "%04d %s\n", i, ins.fmtInstruction(def, operands)) + + i += 1 + read + } + + return out.String() +} + +func (ins Instructions) fmtInstruction(def *Definition, operands []int) string { + operandCount := len(def.OperandWidths) + + if len(operands) != operandCount { + return fmt.Sprintf("ERROR: operand len %d does not match defined %d\n", + len(operands), operandCount) + } + + switch operandCount { + case 0: + return def.Name + case 1: + return fmt.Sprintf("%s %d", def.Name, operands[0]) + } + + return fmt.Sprintf("ERROR: unhandled operandCount for %s\n", def.Name) +} + +type Opcode byte + +const ( + OpConstant Opcode = iota + + OpAdd + + OpPop + + OpSub + OpMul + OpDiv + + OpTrue + OpFalse + + OpEqual + OpNotEqual + OpGreaterThan + + OpMinus + OpBang + + OpJumpNotTruthy + OpJump + + OpNull + + OpGetGlobal + OpSetGlobal + + OpArray + OpHash + OpIndex +) + +type Definition struct { + Name string + OperandWidths []int +} + +var definitions = map[Opcode]*Definition{ + OpConstant: {"OpConstant", []int{2}}, + + OpAdd: {"OpAdd", []int{}}, + + OpPop: {"OpPop", []int{}}, + + OpSub: {"OpSub", []int{}}, + OpMul: {"OpMul", []int{}}, + OpDiv: {"OpDiv", []int{}}, + + OpTrue: {"OpTrue", []int{}}, + OpFalse: {"OpFalse", []int{}}, + + OpEqual: {"OpEqual", []int{}}, + OpNotEqual: {"OpNotEqual", []int{}}, + OpGreaterThan: {"OpGreaterThan", []int{}}, + + OpMinus: {"OpMinus", []int{}}, + OpBang: {"OpBang", []int{}}, + + OpJumpNotTruthy: {"OpJumpNotTruthy", []int{2}}, + OpJump: {"OpJump", []int{2}}, + + OpNull: {"OpNull", []int{}}, + + OpGetGlobal: {"OpGetGlobal", []int{2}}, + OpSetGlobal: {"OpSetGlobal", []int{2}}, + + OpArray: {"OpArray", []int{2}}, + OpHash: {"OpHash", []int{2}}, + OpIndex: {"OpIndex", []int{}}, +} + +func Lookup(op byte) (*Definition, error) { + def, ok := definitions[Opcode(op)] + if !ok { + return nil, fmt.Errorf("opcode %d undefined", op) + } + + return def, nil +} + +func Make(op Opcode, operands ...int) []byte { + def, ok := definitions[op] + if !ok { + return []byte{} + } + + instructionLen := 1 + for _, w := range def.OperandWidths { + instructionLen += w + } + + instruction := make([]byte, instructionLen) + instruction[0] = byte(op) + + offset := 1 + for i, o := range operands { + width := def.OperandWidths[i] + switch width { + case 2: + binary.BigEndian.PutUint16(instruction[offset:], uint16(o)) + } + offset += width + } + + return instruction +} + +func ReadOperands(def *Definition, ins Instructions) ([]int, int) { + operands := make([]int, len(def.OperandWidths)) + offset := 0 + + for i, width := range def.OperandWidths { + switch width { + case 2: + operands[i] = int(ReadUint16(ins[offset:])) + } + + offset += width + } + + return operands, offset +} + +func ReadUint16(ins Instructions) uint16 { + return binary.BigEndian.Uint16(ins) +} diff --git a/wcig_code_1_2/06/src/monkey/code/code_test.go b/wcig_code_1_2/06/src/monkey/code/code_test.go new file mode 100644 index 0000000..a353025 --- /dev/null +++ b/wcig_code_1_2/06/src/monkey/code/code_test.go @@ -0,0 +1,83 @@ +package code + +import "testing" + +func TestMake(t *testing.T) { + tests := []struct { + op Opcode + operands []int + expected []byte + }{ + {OpConstant, []int{65534}, []byte{byte(OpConstant), 255, 254}}, + {OpAdd, []int{}, []byte{byte(OpAdd)}}, + } + + for _, tt := range tests { + instruction := Make(tt.op, tt.operands...) + + if len(instruction) != len(tt.expected) { + t.Errorf("instruction has wrong length. want=%d, got=%d", + len(tt.expected), len(instruction)) + } + + for i, b := range tt.expected { + if instruction[i] != tt.expected[i] { + t.Errorf("wrong byte at pos %d. want=%d, got=%d", + i, b, instruction[i]) + } + } + } +} + +func TestInstructionsString(t *testing.T) { + instructions := []Instructions{ + Make(OpAdd), + Make(OpConstant, 2), + Make(OpConstant, 65535), + } + + expected := `0000 OpAdd +0001 OpConstant 2 +0004 OpConstant 65535 +` + + concatted := Instructions{} + for _, ins := range instructions { + concatted = append(concatted, ins...) + } + + if concatted.String() != expected { + t.Errorf("instructions wrongly formatted.\nwant=%q\ngot=%q", + expected, concatted.String()) + } +} + +func TestReadOperands(t *testing.T) { + tests := []struct { + op Opcode + operands []int + bytesRead int + }{ + {OpConstant, []int{65535}, 2}, + } + + for _, tt := range tests { + instruction := Make(tt.op, tt.operands...) + + def, err := Lookup(byte(tt.op)) + if err != nil { + t.Fatalf("definition not found: %q\n", err) + } + + operandsRead, n := ReadOperands(def, instruction[1:]) + if n != tt.bytesRead { + t.Fatalf("n wrong. want=%d, got=%d", tt.bytesRead, n) + } + + for i, want := range tt.operands { + if operandsRead[i] != want { + t.Errorf("operand wrong. want=%d, got=%d", want, operandsRead[i]) + } + } + } +} diff --git a/wcig_code_1_2/06/src/monkey/compiler/compiler.go b/wcig_code_1_2/06/src/monkey/compiler/compiler.go new file mode 100644 index 0000000..e7fb341 --- /dev/null +++ b/wcig_code_1_2/06/src/monkey/compiler/compiler.go @@ -0,0 +1,306 @@ +package compiler + +import ( + "fmt" + "monkey/ast" + "monkey/code" + "monkey/object" + "sort" +) + +type Compiler struct { + instructions code.Instructions + constants []object.Object + + lastInstruction EmittedInstruction + previousInstruction EmittedInstruction + + symbolTable *SymbolTable +} + +func New() *Compiler { + return &Compiler{ + instructions: code.Instructions{}, + constants: []object.Object{}, + lastInstruction: EmittedInstruction{}, + previousInstruction: EmittedInstruction{}, + symbolTable: NewSymbolTable(), + } +} + +func NewWithState(s *SymbolTable, constants []object.Object) *Compiler { + compiler := New() + compiler.symbolTable = s + compiler.constants = constants + return compiler +} + +func (c *Compiler) Compile(node ast.Node) error { + switch node := node.(type) { + case *ast.Program: + for _, s := range node.Statements { + err := c.Compile(s) + if err != nil { + return err + } + } + + case *ast.ExpressionStatement: + err := c.Compile(node.Expression) + if err != nil { + return err + } + c.emit(code.OpPop) + + case *ast.InfixExpression: + if node.Operator == "<" { + err := c.Compile(node.Right) + if err != nil { + return err + } + + err = c.Compile(node.Left) + if err != nil { + return err + } + c.emit(code.OpGreaterThan) + return nil + } + + err := c.Compile(node.Left) + if err != nil { + return err + } + + err = c.Compile(node.Right) + if err != nil { + return err + } + + switch node.Operator { + case "+": + c.emit(code.OpAdd) + case "-": + c.emit(code.OpSub) + case "*": + c.emit(code.OpMul) + case "/": + c.emit(code.OpDiv) + case ">": + c.emit(code.OpGreaterThan) + case "==": + c.emit(code.OpEqual) + case "!=": + c.emit(code.OpNotEqual) + default: + return fmt.Errorf("unknown operator %s", node.Operator) + } + + case *ast.IntegerLiteral: + integer := &object.Integer{Value: node.Value} + c.emit(code.OpConstant, c.addConstant(integer)) + + case *ast.Boolean: + if node.Value { + c.emit(code.OpTrue) + } else { + c.emit(code.OpFalse) + } + + case *ast.PrefixExpression: + err := c.Compile(node.Right) + if err != nil { + return err + } + + switch node.Operator { + case "!": + c.emit(code.OpBang) + case "-": + c.emit(code.OpMinus) + default: + return fmt.Errorf("unknown operator %s", node.Operator) + } + + case *ast.IfExpression: + err := c.Compile(node.Condition) + if err != nil { + return err + } + + // Emit an `OpJumpNotTruthy` with a bogus value + jumpNotTruthyPos := c.emit(code.OpJumpNotTruthy, 9999) + + err = c.Compile(node.Consequence) + if err != nil { + return err + } + + if c.lastInstructionIsPop() { + c.removeLastPop() + } + + // Emit an `OpJump` with a bogus value + jumpPos := c.emit(code.OpJump, 9999) + + afterConsequencePos := len(c.instructions) + c.changeOperand(jumpNotTruthyPos, afterConsequencePos) + + if node.Alternative == nil { + c.emit(code.OpNull) + } else { + err := c.Compile(node.Alternative) + if err != nil { + return err + } + + if c.lastInstructionIsPop() { + c.removeLastPop() + } + } + + afterAlternativePos := len(c.instructions) + c.changeOperand(jumpPos, afterAlternativePos) + + case *ast.BlockStatement: + for _, s := range node.Statements { + err := c.Compile(s) + if err != nil { + return err + } + } + + case *ast.LetStatement: + err := c.Compile(node.Value) + if err != nil { + return err + } + symbol := c.symbolTable.Define(node.Name.Value) + c.emit(code.OpSetGlobal, symbol.Index) + + case *ast.Identifier: + symbol, ok := c.symbolTable.Resolve(node.Value) + if !ok { + return fmt.Errorf("undefined variable %s", node.Value) + } + c.emit(code.OpGetGlobal, symbol.Index) + + case *ast.StringLiteral: + str := &object.String{Value: node.Value} + c.emit(code.OpConstant, c.addConstant(str)) + + case *ast.ArrayLiteral: + for _, el := range node.Elements { + err := c.Compile(el) + if err != nil { + return err + } + } + + c.emit(code.OpArray, len(node.Elements)) + + case *ast.HashLiteral: + keys := []ast.Expression{} + for k := range node.Pairs { + keys = append(keys, k) + } + sort.Slice(keys, func(i, j int) bool { + return keys[i].String() < keys[j].String() + }) + + for _, k := range keys { + err := c.Compile(k) + if err != nil { + return err + } + err = c.Compile(node.Pairs[k]) + if err != nil { + return err + } + } + + c.emit(code.OpHash, len(node.Pairs)*2) + + case *ast.IndexExpression: + err := c.Compile(node.Left) + if err != nil { + return err + } + + err = c.Compile(node.Index) + if err != nil { + return err + } + + c.emit(code.OpIndex) + } + + return nil +} + +func (c *Compiler) Bytecode() *Bytecode { + return &Bytecode{ + Instructions: c.instructions, + Constants: c.constants, + } +} + +func (c *Compiler) addConstant(obj object.Object) int { + c.constants = append(c.constants, obj) + return len(c.constants) - 1 +} + +func (c *Compiler) emit(op code.Opcode, operands ...int) int { + ins := code.Make(op, operands...) + pos := c.addInstruction(ins) + + c.setLastInstruction(op, pos) + + return pos +} + +func (c *Compiler) addInstruction(ins []byte) int { + posNewInstruction := len(c.instructions) + c.instructions = append(c.instructions, ins...) + return posNewInstruction +} + +func (c *Compiler) setLastInstruction(op code.Opcode, pos int) { + previous := c.lastInstruction + last := EmittedInstruction{Opcode: op, Position: pos} + + c.previousInstruction = previous + c.lastInstruction = last +} + +func (c *Compiler) lastInstructionIsPop() bool { + return c.lastInstruction.Opcode == code.OpPop +} + +func (c *Compiler) removeLastPop() { + c.instructions = c.instructions[:c.lastInstruction.Position] + c.lastInstruction = c.previousInstruction +} + +func (c *Compiler) replaceInstruction(pos int, newInstruction []byte) { + for i := 0; i < len(newInstruction); i++ { + c.instructions[pos+i] = newInstruction[i] + } +} + +func (c *Compiler) changeOperand(opPos int, operand int) { + op := code.Opcode(c.instructions[opPos]) + newInstruction := code.Make(op, operand) + + c.replaceInstruction(opPos, newInstruction) +} + +type Bytecode struct { + Instructions code.Instructions + Constants []object.Object +} + +type EmittedInstruction struct { + Opcode code.Opcode + Position int +} diff --git a/wcig_code_1_2/06/src/monkey/compiler/compiler_test.go b/wcig_code_1_2/06/src/monkey/compiler/compiler_test.go new file mode 100644 index 0000000..c69eb5a --- /dev/null +++ b/wcig_code_1_2/06/src/monkey/compiler/compiler_test.go @@ -0,0 +1,551 @@ +package compiler + +import ( + "fmt" + "monkey/ast" + "monkey/code" + "monkey/lexer" + "monkey/object" + "monkey/parser" + "testing" +) + +func TestIntegerArithmetic(t *testing.T) { + tests := []compilerTestCase{ + { + input: "1 + 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpAdd), + code.Make(code.OpPop), + }, + }, + { + input: "1; 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpPop), + code.Make(code.OpConstant, 1), + code.Make(code.OpPop), + }, + }, + { + input: "1 - 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpSub), + code.Make(code.OpPop), + }, + }, + { + input: "1 * 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpMul), + code.Make(code.OpPop), + }, + }, + { + input: "2 / 1", + expectedConstants: []interface{}{2, 1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpDiv), + code.Make(code.OpPop), + }, + }, + { + input: "-1", + expectedConstants: []interface{}{1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpMinus), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestBooleanExpressions(t *testing.T) { + tests := []compilerTestCase{ + { + input: "true", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpTrue), + code.Make(code.OpPop), + }, + }, + { + input: "false", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpFalse), + code.Make(code.OpPop), + }, + }, + { + input: "1 > 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpGreaterThan), + code.Make(code.OpPop), + }, + }, + { + input: "1 < 2", + expectedConstants: []interface{}{2, 1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpGreaterThan), + code.Make(code.OpPop), + }, + }, + { + input: "1 == 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpEqual), + code.Make(code.OpPop), + }, + }, + { + input: "1 != 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpNotEqual), + code.Make(code.OpPop), + }, + }, + { + input: "true == false", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpTrue), + code.Make(code.OpFalse), + code.Make(code.OpEqual), + code.Make(code.OpPop), + }, + }, + { + input: "true != false", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpTrue), + code.Make(code.OpFalse), + code.Make(code.OpNotEqual), + code.Make(code.OpPop), + }, + }, + { + input: "!true", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpTrue), + code.Make(code.OpBang), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestConditionals(t *testing.T) { + tests := []compilerTestCase{ + { + input: ` + if (true) { 10 }; 3333; + `, + expectedConstants: []interface{}{10, 3333}, + expectedInstructions: []code.Instructions{ + // 0000 + code.Make(code.OpTrue), + // 0001 + code.Make(code.OpJumpNotTruthy, 10), + // 0004 + code.Make(code.OpConstant, 0), + // 0007 + code.Make(code.OpJump, 11), + // 0010 + code.Make(code.OpNull), + // 0011 + code.Make(code.OpPop), + // 0012 + code.Make(code.OpConstant, 1), + // 0015 + code.Make(code.OpPop), + }, + }, + { + input: ` + if (true) { 10 } else { 20 }; 3333; + `, + expectedConstants: []interface{}{10, 20, 3333}, + expectedInstructions: []code.Instructions{ + // 0000 + code.Make(code.OpTrue), + // 0001 + code.Make(code.OpJumpNotTruthy, 10), + // 0004 + code.Make(code.OpConstant, 0), + // 0007 + code.Make(code.OpJump, 13), + // 0010 + code.Make(code.OpConstant, 1), + // 0013 + code.Make(code.OpPop), + // 0014 + code.Make(code.OpConstant, 2), + // 0017 + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestGlobalLetStatements(t *testing.T) { + tests := []compilerTestCase{ + { + input: ` + let one = 1; + let two = 2; + `, + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpSetGlobal, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpSetGlobal, 1), + }, + }, + { + input: ` + let one = 1; + one; + `, + expectedConstants: []interface{}{1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpSetGlobal, 0), + code.Make(code.OpGetGlobal, 0), + code.Make(code.OpPop), + }, + }, + { + input: ` + let one = 1; + let two = one; + two; + `, + expectedConstants: []interface{}{1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpSetGlobal, 0), + code.Make(code.OpGetGlobal, 0), + code.Make(code.OpSetGlobal, 1), + code.Make(code.OpGetGlobal, 1), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +type compilerTestCase struct { + input string + expectedConstants []interface{} + expectedInstructions []code.Instructions +} + +func runCompilerTests(t *testing.T, tests []compilerTestCase) { + t.Helper() + + for _, tt := range tests { + program := parse(tt.input) + + compiler := New() + err := compiler.Compile(program) + if err != nil { + t.Fatalf("compiler error: %s", err) + } + + bytecode := compiler.Bytecode() + + err = testInstructions(tt.expectedInstructions, bytecode.Instructions) + if err != nil { + t.Fatalf("testInstructions failed: %s", err) + } + + err = testConstants(t, tt.expectedConstants, bytecode.Constants) + if err != nil { + t.Fatalf("testConstants failed: %s", err) + } + } +} + +func TestStringExpressions(t *testing.T) { + tests := []compilerTestCase{ + { + input: `"monkey"`, + expectedConstants: []interface{}{"monkey"}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpPop), + }, + }, + { + input: `"mon" + "key"`, + expectedConstants: []interface{}{"mon", "key"}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpAdd), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestArrayLiterals(t *testing.T) { + tests := []compilerTestCase{ + { + input: "[]", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpArray, 0), + code.Make(code.OpPop), + }, + }, + { + input: "[1, 2, 3]", + expectedConstants: []interface{}{1, 2, 3}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpConstant, 2), + code.Make(code.OpArray, 3), + code.Make(code.OpPop), + }, + }, + { + input: "[1 + 2, 3 - 4, 5 * 6]", + expectedConstants: []interface{}{1, 2, 3, 4, 5, 6}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpAdd), + code.Make(code.OpConstant, 2), + code.Make(code.OpConstant, 3), + code.Make(code.OpSub), + code.Make(code.OpConstant, 4), + code.Make(code.OpConstant, 5), + code.Make(code.OpMul), + code.Make(code.OpArray, 3), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestHashLiterals(t *testing.T) { + tests := []compilerTestCase{ + { + input: "{}", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpHash, 0), + code.Make(code.OpPop), + }, + }, + { + input: "{1: 2, 3: 4, 5: 6}", + expectedConstants: []interface{}{1, 2, 3, 4, 5, 6}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpConstant, 2), + code.Make(code.OpConstant, 3), + code.Make(code.OpConstant, 4), + code.Make(code.OpConstant, 5), + code.Make(code.OpHash, 6), + code.Make(code.OpPop), + }, + }, + { + input: "{1: 2 + 3, 4: 5 * 6}", + expectedConstants: []interface{}{1, 2, 3, 4, 5, 6}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpConstant, 2), + code.Make(code.OpAdd), + code.Make(code.OpConstant, 3), + code.Make(code.OpConstant, 4), + code.Make(code.OpConstant, 5), + code.Make(code.OpMul), + code.Make(code.OpHash, 4), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestIndexExpressions(t *testing.T) { + tests := []compilerTestCase{ + { + input: "[1, 2, 3][1 + 1]", + expectedConstants: []interface{}{1, 2, 3, 1, 1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpConstant, 2), + code.Make(code.OpArray, 3), + code.Make(code.OpConstant, 3), + code.Make(code.OpConstant, 4), + code.Make(code.OpAdd), + code.Make(code.OpIndex), + code.Make(code.OpPop), + }, + }, + { + input: "{1: 2}[2 - 1]", + expectedConstants: []interface{}{1, 2, 2, 1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpHash, 2), + code.Make(code.OpConstant, 2), + code.Make(code.OpConstant, 3), + code.Make(code.OpSub), + code.Make(code.OpIndex), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func parse(input string) *ast.Program { + l := lexer.New(input) + p := parser.New(l) + return p.ParseProgram() +} + +func testInstructions( + expected []code.Instructions, + actual code.Instructions, +) error { + concatted := concatInstructions(expected) + + if len(actual) != len(concatted) { + return fmt.Errorf("wrong instructions length.\nwant=%q\ngot =%q", + concatted, actual) + } + + for i, ins := range concatted { + if actual[i] != ins { + return fmt.Errorf("wrong instruction at %d.\nwant=%q\ngot =%q", + i, concatted, actual) + } + } + + return nil +} + +func concatInstructions(s []code.Instructions) code.Instructions { + out := code.Instructions{} + + for _, ins := range s { + out = append(out, ins...) + } + + return out +} + +func testConstants( + t *testing.T, + expected []interface{}, + actual []object.Object, +) error { + if len(expected) != len(actual) { + return fmt.Errorf("wrong number of constants. got=%d, want=%d", + len(actual), len(expected)) + } + + for i, constant := range expected { + switch constant := constant.(type) { + case string: + err := testStringObject(constant, actual[i]) + if err != nil { + return fmt.Errorf("constant %d - testStringObject failed: %s", + i, err) + } + case int: + err := testIntegerObject(int64(constant), actual[i]) + if err != nil { + return fmt.Errorf("constant %d - testIntegerObject failed: %s", + i, err) + } + } + } + + return nil +} + +func testIntegerObject(expected int64, actual object.Object) error { + result, ok := actual.(*object.Integer) + if !ok { + return fmt.Errorf("object is not Integer. got=%T (%+v)", + actual, actual) + } + + if result.Value != expected { + return fmt.Errorf("object has wrong value. got=%d, want=%d", + result.Value, expected) + } + + return nil +} + +func testStringObject(expected string, actual object.Object) error { + result, ok := actual.(*object.String) + if !ok { + return fmt.Errorf("object is not String. got=%T (%+v)", + actual, actual) + } + + if result.Value != expected { + return fmt.Errorf("object has wrong value. got=%q, want=%q", + result.Value, expected) + } + + return nil +} diff --git a/wcig_code_1_2/06/src/monkey/compiler/symbol_table.go b/wcig_code_1_2/06/src/monkey/compiler/symbol_table.go new file mode 100644 index 0000000..80303a7 --- /dev/null +++ b/wcig_code_1_2/06/src/monkey/compiler/symbol_table.go @@ -0,0 +1,35 @@ +package compiler + +type SymbolScope string + +const ( + GlobalScope SymbolScope = "GLOBAL" +) + +type Symbol struct { + Name string + Scope SymbolScope + Index int +} + +type SymbolTable struct { + store map[string]Symbol + numDefinitions int +} + +func NewSymbolTable() *SymbolTable { + s := make(map[string]Symbol) + return &SymbolTable{store: s} +} + +func (s *SymbolTable) Define(name string) Symbol { + symbol := Symbol{Name: name, Index: s.numDefinitions, Scope: GlobalScope} + s.store[name] = symbol + s.numDefinitions++ + return symbol +} + +func (s *SymbolTable) Resolve(name string) (Symbol, bool) { + obj, ok := s.store[name] + return obj, ok +} diff --git a/wcig_code_1_2/06/src/monkey/compiler/symbol_table_test.go b/wcig_code_1_2/06/src/monkey/compiler/symbol_table_test.go new file mode 100644 index 0000000..c037afc --- /dev/null +++ b/wcig_code_1_2/06/src/monkey/compiler/symbol_table_test.go @@ -0,0 +1,45 @@ +package compiler + +import "testing" + +func TestDefine(t *testing.T) { + expected := map[string]Symbol{ + "a": Symbol{Name: "a", Scope: GlobalScope, Index: 0}, + "b": Symbol{Name: "b", Scope: GlobalScope, Index: 1}, + } + + global := NewSymbolTable() + + a := global.Define("a") + if a != expected["a"] { + t.Errorf("expected a=%+v, got=%+v", expected["a"], a) + } + + b := global.Define("b") + if b != expected["b"] { + t.Errorf("expected b=%+v, got=%+v", expected["b"], b) + } +} + +func TestResolveGlobal(t *testing.T) { + global := NewSymbolTable() + global.Define("a") + global.Define("b") + + expected := []Symbol{ + Symbol{Name: "a", Scope: GlobalScope, Index: 0}, + Symbol{Name: "b", Scope: GlobalScope, Index: 1}, + } + + for _, sym := range expected { + result, ok := global.Resolve(sym.Name) + if !ok { + t.Errorf("name %s not resolvable", sym.Name) + continue + } + if result != sym { + t.Errorf("expected %s to resolve to %+v, got=%+v", + sym.Name, sym, result) + } + } +} diff --git a/wcig_code_1_2/06/src/monkey/evaluator/builtins.go b/wcig_code_1_2/06/src/monkey/evaluator/builtins.go new file mode 100644 index 0000000..c4fb0f3 --- /dev/null +++ b/wcig_code_1_2/06/src/monkey/evaluator/builtins.go @@ -0,0 +1,117 @@ +package evaluator + +import ( + "fmt" + "monkey/object" +) + +var builtins = map[string]*object.Builtin{ + "len": &object.Builtin{Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", + len(args)) + } + + switch arg := args[0].(type) { + case *object.Array: + return &object.Integer{Value: int64(len(arg.Elements))} + case *object.String: + return &object.Integer{Value: int64(len(arg.Value))} + default: + return newError("argument to `len` not supported, got %s", + args[0].Type()) + } + }, + }, + "puts": &object.Builtin{ + Fn: func(args ...object.Object) object.Object { + for _, arg := range args { + fmt.Println(arg.Inspect()) + } + + return NULL + }, + }, + "first": &object.Builtin{ + Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", + len(args)) + } + if args[0].Type() != object.ARRAY_OBJ { + return newError("argument to `first` must be ARRAY, got %s", + args[0].Type()) + } + + arr := args[0].(*object.Array) + if len(arr.Elements) > 0 { + return arr.Elements[0] + } + + return NULL + }, + }, + "last": &object.Builtin{ + Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", + len(args)) + } + if args[0].Type() != object.ARRAY_OBJ { + return newError("argument to `last` must be ARRAY, got %s", + args[0].Type()) + } + + arr := args[0].(*object.Array) + length := len(arr.Elements) + if length > 0 { + return arr.Elements[length-1] + } + + return NULL + }, + }, + "rest": &object.Builtin{ + Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", + len(args)) + } + if args[0].Type() != object.ARRAY_OBJ { + return newError("argument to `rest` must be ARRAY, got %s", + args[0].Type()) + } + + arr := args[0].(*object.Array) + length := len(arr.Elements) + if length > 0 { + newElements := make([]object.Object, length-1, length-1) + copy(newElements, arr.Elements[1:length]) + return &object.Array{Elements: newElements} + } + + return NULL + }, + }, + "push": &object.Builtin{ + Fn: func(args ...object.Object) object.Object { + if len(args) != 2 { + return newError("wrong number of arguments. got=%d, want=2", + len(args)) + } + if args[0].Type() != object.ARRAY_OBJ { + return newError("argument to `push` must be ARRAY, got %s", + args[0].Type()) + } + + arr := args[0].(*object.Array) + length := len(arr.Elements) + + newElements := make([]object.Object, length+1, length+1) + copy(newElements, arr.Elements) + newElements[length] = args[1] + + return &object.Array{Elements: newElements} + }, + }, +} diff --git a/wcig_code_1_2/06/src/monkey/evaluator/evaluator.go b/wcig_code_1_2/06/src/monkey/evaluator/evaluator.go new file mode 100644 index 0000000..50b2ab5 --- /dev/null +++ b/wcig_code_1_2/06/src/monkey/evaluator/evaluator.go @@ -0,0 +1,442 @@ +package evaluator + +import ( + "fmt" + "monkey/ast" + "monkey/object" +) + +var ( + NULL = &object.Null{} + TRUE = &object.Boolean{Value: true} + FALSE = &object.Boolean{Value: false} +) + +func Eval(node ast.Node, env *object.Environment) object.Object { + switch node := node.(type) { + + // Statements + case *ast.Program: + return evalProgram(node, env) + + case *ast.BlockStatement: + return evalBlockStatement(node, env) + + case *ast.ExpressionStatement: + return Eval(node.Expression, env) + + case *ast.ReturnStatement: + val := Eval(node.ReturnValue, env) + if isError(val) { + return val + } + return &object.ReturnValue{Value: val} + + case *ast.LetStatement: + val := Eval(node.Value, env) + if isError(val) { + return val + } + env.Set(node.Name.Value, val) + + // Expressions + case *ast.IntegerLiteral: + return &object.Integer{Value: node.Value} + + case *ast.StringLiteral: + return &object.String{Value: node.Value} + + case *ast.Boolean: + return nativeBoolToBooleanObject(node.Value) + + case *ast.PrefixExpression: + right := Eval(node.Right, env) + if isError(right) { + return right + } + return evalPrefixExpression(node.Operator, right) + + case *ast.InfixExpression: + left := Eval(node.Left, env) + if isError(left) { + return left + } + + right := Eval(node.Right, env) + if isError(right) { + return right + } + + return evalInfixExpression(node.Operator, left, right) + + case *ast.IfExpression: + return evalIfExpression(node, env) + + case *ast.Identifier: + return evalIdentifier(node, env) + + case *ast.FunctionLiteral: + params := node.Parameters + body := node.Body + return &object.Function{Parameters: params, Env: env, Body: body} + + case *ast.CallExpression: + function := Eval(node.Function, env) + if isError(function) { + return function + } + + args := evalExpressions(node.Arguments, env) + if len(args) == 1 && isError(args[0]) { + return args[0] + } + + return applyFunction(function, args) + + case *ast.ArrayLiteral: + elements := evalExpressions(node.Elements, env) + if len(elements) == 1 && isError(elements[0]) { + return elements[0] + } + return &object.Array{Elements: elements} + + case *ast.IndexExpression: + left := Eval(node.Left, env) + if isError(left) { + return left + } + index := Eval(node.Index, env) + if isError(index) { + return index + } + return evalIndexExpression(left, index) + + case *ast.HashLiteral: + return evalHashLiteral(node, env) + + } + + return nil +} + +func evalProgram(program *ast.Program, env *object.Environment) object.Object { + var result object.Object + + for _, statement := range program.Statements { + result = Eval(statement, env) + + switch result := result.(type) { + case *object.ReturnValue: + return result.Value + case *object.Error: + return result + } + } + + return result +} + +func evalBlockStatement( + block *ast.BlockStatement, + env *object.Environment, +) object.Object { + var result object.Object + + for _, statement := range block.Statements { + result = Eval(statement, env) + + if result != nil { + rt := result.Type() + if rt == object.RETURN_VALUE_OBJ || rt == object.ERROR_OBJ { + return result + } + } + } + + return result +} + +func nativeBoolToBooleanObject(input bool) *object.Boolean { + if input { + return TRUE + } + return FALSE +} + +func evalPrefixExpression(operator string, right object.Object) object.Object { + switch operator { + case "!": + return evalBangOperatorExpression(right) + case "-": + return evalMinusPrefixOperatorExpression(right) + default: + return newError("unknown operator: %s%s", operator, right.Type()) + } +} + +func evalInfixExpression( + operator string, + left, right object.Object, +) object.Object { + switch { + case left.Type() == object.INTEGER_OBJ && right.Type() == object.INTEGER_OBJ: + return evalIntegerInfixExpression(operator, left, right) + case left.Type() == object.STRING_OBJ && right.Type() == object.STRING_OBJ: + return evalStringInfixExpression(operator, left, right) + case operator == "==": + return nativeBoolToBooleanObject(left == right) + case operator == "!=": + return nativeBoolToBooleanObject(left != right) + case left.Type() != right.Type(): + return newError("type mismatch: %s %s %s", + left.Type(), operator, right.Type()) + default: + return newError("unknown operator: %s %s %s", + left.Type(), operator, right.Type()) + } +} + +func evalBangOperatorExpression(right object.Object) object.Object { + switch right { + case TRUE: + return FALSE + case FALSE: + return TRUE + case NULL: + return TRUE + default: + return FALSE + } +} + +func evalMinusPrefixOperatorExpression(right object.Object) object.Object { + if right.Type() != object.INTEGER_OBJ { + return newError("unknown operator: -%s", right.Type()) + } + + value := right.(*object.Integer).Value + return &object.Integer{Value: -value} +} + +func evalIntegerInfixExpression( + operator string, + left, right object.Object, +) object.Object { + leftVal := left.(*object.Integer).Value + rightVal := right.(*object.Integer).Value + + switch operator { + case "+": + return &object.Integer{Value: leftVal + rightVal} + case "-": + return &object.Integer{Value: leftVal - rightVal} + case "*": + return &object.Integer{Value: leftVal * rightVal} + case "/": + return &object.Integer{Value: leftVal / rightVal} + case "<": + return nativeBoolToBooleanObject(leftVal < rightVal) + case ">": + return nativeBoolToBooleanObject(leftVal > rightVal) + case "==": + return nativeBoolToBooleanObject(leftVal == rightVal) + case "!=": + return nativeBoolToBooleanObject(leftVal != rightVal) + default: + return newError("unknown operator: %s %s %s", + left.Type(), operator, right.Type()) + } +} + +func evalStringInfixExpression( + operator string, + left, right object.Object, +) object.Object { + if operator != "+" { + return newError("unknown operator: %s %s %s", + left.Type(), operator, right.Type()) + } + + leftVal := left.(*object.String).Value + rightVal := right.(*object.String).Value + return &object.String{Value: leftVal + rightVal} +} + +func evalIfExpression( + ie *ast.IfExpression, + env *object.Environment, +) object.Object { + condition := Eval(ie.Condition, env) + if isError(condition) { + return condition + } + + if isTruthy(condition) { + return Eval(ie.Consequence, env) + } else if ie.Alternative != nil { + return Eval(ie.Alternative, env) + } else { + return NULL + } +} + +func evalIdentifier( + node *ast.Identifier, + env *object.Environment, +) object.Object { + if val, ok := env.Get(node.Value); ok { + return val + } + + if builtin, ok := builtins[node.Value]; ok { + return builtin + } + + return newError("identifier not found: " + node.Value) +} + +func isTruthy(obj object.Object) bool { + switch obj { + case NULL: + return false + case TRUE: + return true + case FALSE: + return false + default: + return true + } +} + +func newError(format string, a ...interface{}) *object.Error { + return &object.Error{Message: fmt.Sprintf(format, a...)} +} + +func isError(obj object.Object) bool { + if obj != nil { + return obj.Type() == object.ERROR_OBJ + } + return false +} + +func evalExpressions( + exps []ast.Expression, + env *object.Environment, +) []object.Object { + var result []object.Object + + for _, e := range exps { + evaluated := Eval(e, env) + if isError(evaluated) { + return []object.Object{evaluated} + } + result = append(result, evaluated) + } + + return result +} + +func applyFunction(fn object.Object, args []object.Object) object.Object { + switch fn := fn.(type) { + + case *object.Function: + extendedEnv := extendFunctionEnv(fn, args) + evaluated := Eval(fn.Body, extendedEnv) + return unwrapReturnValue(evaluated) + + case *object.Builtin: + return fn.Fn(args...) + + default: + return newError("not a function: %s", fn.Type()) + } +} + +func extendFunctionEnv( + fn *object.Function, + args []object.Object, +) *object.Environment { + env := object.NewEnclosedEnvironment(fn.Env) + + for paramIdx, param := range fn.Parameters { + env.Set(param.Value, args[paramIdx]) + } + + return env +} + +func unwrapReturnValue(obj object.Object) object.Object { + if returnValue, ok := obj.(*object.ReturnValue); ok { + return returnValue.Value + } + + return obj +} + +func evalIndexExpression(left, index object.Object) object.Object { + switch { + case left.Type() == object.ARRAY_OBJ && index.Type() == object.INTEGER_OBJ: + return evalArrayIndexExpression(left, index) + case left.Type() == object.HASH_OBJ: + return evalHashIndexExpression(left, index) + default: + return newError("index operator not supported: %s", left.Type()) + } +} + +func evalArrayIndexExpression(array, index object.Object) object.Object { + arrayObject := array.(*object.Array) + idx := index.(*object.Integer).Value + max := int64(len(arrayObject.Elements) - 1) + + if idx < 0 || idx > max { + return NULL + } + + return arrayObject.Elements[idx] +} + +func evalHashLiteral( + node *ast.HashLiteral, + env *object.Environment, +) object.Object { + pairs := make(map[object.HashKey]object.HashPair) + + for keyNode, valueNode := range node.Pairs { + key := Eval(keyNode, env) + if isError(key) { + return key + } + + hashKey, ok := key.(object.Hashable) + if !ok { + return newError("unusable as hash key: %s", key.Type()) + } + + value := Eval(valueNode, env) + if isError(value) { + return value + } + + hashed := hashKey.HashKey() + pairs[hashed] = object.HashPair{Key: key, Value: value} + } + + return &object.Hash{Pairs: pairs} +} + +func evalHashIndexExpression(hash, index object.Object) object.Object { + hashObject := hash.(*object.Hash) + + key, ok := index.(object.Hashable) + if !ok { + return newError("unusable as hash key: %s", index.Type()) + } + + pair, ok := hashObject.Pairs[key.HashKey()] + if !ok { + return NULL + } + + return pair.Value +} diff --git a/wcig_code_1_2/06/src/monkey/evaluator/evaluator_test.go b/wcig_code_1_2/06/src/monkey/evaluator/evaluator_test.go new file mode 100644 index 0000000..2304e6f --- /dev/null +++ b/wcig_code_1_2/06/src/monkey/evaluator/evaluator_test.go @@ -0,0 +1,629 @@ +package evaluator + +import ( + "monkey/lexer" + "monkey/object" + "monkey/parser" + "testing" +) + +func TestEvalIntegerExpression(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"5", 5}, + {"10", 10}, + {"-5", -5}, + {"-10", -10}, + {"5 + 5 + 5 + 5 - 10", 10}, + {"2 * 2 * 2 * 2 * 2", 32}, + {"-50 + 100 + -50", 0}, + {"5 * 2 + 10", 20}, + {"5 + 2 * 10", 25}, + {"20 + 2 * -10", 0}, + {"50 / 2 * 2 + 10", 60}, + {"2 * (5 + 10)", 30}, + {"3 * 3 * 3 + 10", 37}, + {"3 * (3 * 3) + 10", 37}, + {"(5 + 10 * 2 + 15 / 3) * 2 + -10", 50}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testIntegerObject(t, evaluated, tt.expected) + } +} + +func TestEvalBooleanExpression(t *testing.T) { + tests := []struct { + input string + expected bool + }{ + {"true", true}, + {"false", false}, + {"1 < 2", true}, + {"1 > 2", false}, + {"1 < 1", false}, + {"1 > 1", false}, + {"1 == 1", true}, + {"1 != 1", false}, + {"1 == 2", false}, + {"1 != 2", true}, + {"true == true", true}, + {"false == false", true}, + {"true == false", false}, + {"true != false", true}, + {"false != true", true}, + {"(1 < 2) == true", true}, + {"(1 < 2) == false", false}, + {"(1 > 2) == true", false}, + {"(1 > 2) == false", true}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testBooleanObject(t, evaluated, tt.expected) + } +} + +func TestBangOperator(t *testing.T) { + tests := []struct { + input string + expected bool + }{ + {"!true", false}, + {"!false", true}, + {"!5", false}, + {"!!true", true}, + {"!!false", false}, + {"!!5", true}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testBooleanObject(t, evaluated, tt.expected) + } +} + +func TestIfElseExpressions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + {"if (true) { 10 }", 10}, + {"if (false) { 10 }", nil}, + {"if (1) { 10 }", 10}, + {"if (1 < 2) { 10 }", 10}, + {"if (1 > 2) { 10 }", nil}, + {"if (1 > 2) { 10 } else { 20 }", 20}, + {"if (1 < 2) { 10 } else { 20 }", 10}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + integer, ok := tt.expected.(int) + if ok { + testIntegerObject(t, evaluated, int64(integer)) + } else { + testNullObject(t, evaluated) + } + } +} + +func TestReturnStatements(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"return 10;", 10}, + {"return 10; 9;", 10}, + {"return 2 * 5; 9;", 10}, + {"9; return 2 * 5; 9;", 10}, + {"if (10 > 1) { return 10; }", 10}, + { + ` +if (10 > 1) { + if (10 > 1) { + return 10; + } + + return 1; +} +`, + 10, + }, + { + ` +let f = fn(x) { + return x; + x + 10; +}; +f(10);`, + 10, + }, + { + ` +let f = fn(x) { + let result = x + 10; + return result; + return 10; +}; +f(10);`, + 20, + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testIntegerObject(t, evaluated, tt.expected) + } +} + +func TestErrorHandling(t *testing.T) { + tests := []struct { + input string + expectedMessage string + }{ + { + "5 + true;", + "type mismatch: INTEGER + BOOLEAN", + }, + { + "5 + true; 5;", + "type mismatch: INTEGER + BOOLEAN", + }, + { + "-true", + "unknown operator: -BOOLEAN", + }, + { + "true + false;", + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + "true + false + true + false;", + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + "5; true + false; 5", + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + `"Hello" - "World"`, + "unknown operator: STRING - STRING", + }, + { + "if (10 > 1) { true + false; }", + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + ` +if (10 > 1) { + if (10 > 1) { + return true + false; + } + + return 1; +} +`, + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + "foobar", + "identifier not found: foobar", + }, + { + `{"name": "Monkey"}[fn(x) { x }];`, + "unusable as hash key: FUNCTION", + }, + { + `999[1]`, + "index operator not supported: INTEGER", + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + + errObj, ok := evaluated.(*object.Error) + if !ok { + t.Errorf("no error object returned. got=%T(%+v)", + evaluated, evaluated) + continue + } + + if errObj.Message != tt.expectedMessage { + t.Errorf("wrong error message. expected=%q, got=%q", + tt.expectedMessage, errObj.Message) + } + } +} + +func TestLetStatements(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"let a = 5; a;", 5}, + {"let a = 5 * 5; a;", 25}, + {"let a = 5; let b = a; b;", 5}, + {"let a = 5; let b = a; let c = a + b + 5; c;", 15}, + } + + for _, tt := range tests { + testIntegerObject(t, testEval(tt.input), tt.expected) + } +} + +func TestFunctionObject(t *testing.T) { + input := "fn(x) { x + 2; };" + + evaluated := testEval(input) + fn, ok := evaluated.(*object.Function) + if !ok { + t.Fatalf("object is not Function. got=%T (%+v)", evaluated, evaluated) + } + + if len(fn.Parameters) != 1 { + t.Fatalf("function has wrong parameters. Parameters=%+v", + fn.Parameters) + } + + if fn.Parameters[0].String() != "x" { + t.Fatalf("parameter is not 'x'. got=%q", fn.Parameters[0]) + } + + expectedBody := "(x + 2)" + + if fn.Body.String() != expectedBody { + t.Fatalf("body is not %q. got=%q", expectedBody, fn.Body.String()) + } +} + +func TestFunctionApplication(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"let identity = fn(x) { x; }; identity(5);", 5}, + {"let identity = fn(x) { return x; }; identity(5);", 5}, + {"let double = fn(x) { x * 2; }; double(5);", 10}, + {"let add = fn(x, y) { x + y; }; add(5, 5);", 10}, + {"let add = fn(x, y) { x + y; }; add(5 + 5, add(5, 5));", 20}, + {"fn(x) { x; }(5)", 5}, + } + + for _, tt := range tests { + testIntegerObject(t, testEval(tt.input), tt.expected) + } +} + +func TestEnclosingEnvironments(t *testing.T) { + input := ` +let first = 10; +let second = 10; +let third = 10; + +let ourFunction = fn(first) { + let second = 20; + + first + second + third; +}; + +ourFunction(20) + first + second;` + + testIntegerObject(t, testEval(input), 70) +} + +func TestClosures(t *testing.T) { + input := ` +let newAdder = fn(x) { + fn(y) { x + y }; +}; + +let addTwo = newAdder(2); +addTwo(2);` + + testIntegerObject(t, testEval(input), 4) +} + +func TestStringLiteral(t *testing.T) { + input := `"Hello World!"` + + evaluated := testEval(input) + str, ok := evaluated.(*object.String) + if !ok { + t.Fatalf("object is not String. got=%T (%+v)", evaluated, evaluated) + } + + if str.Value != "Hello World!" { + t.Errorf("String has wrong value. got=%q", str.Value) + } +} + +func TestStringConcatenation(t *testing.T) { + input := `"Hello" + " " + "World!"` + + evaluated := testEval(input) + str, ok := evaluated.(*object.String) + if !ok { + t.Fatalf("object is not String. got=%T (%+v)", evaluated, evaluated) + } + + if str.Value != "Hello World!" { + t.Errorf("String has wrong value. got=%q", str.Value) + } +} + +func TestBuiltinFunctions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + {`len("")`, 0}, + {`len("four")`, 4}, + {`len("hello world")`, 11}, + {`len(1)`, "argument to `len` not supported, got INTEGER"}, + {`len("one", "two")`, "wrong number of arguments. got=2, want=1"}, + {`len([1, 2, 3])`, 3}, + {`len([])`, 0}, + {`puts("hello", "world!")`, nil}, + {`first([1, 2, 3])`, 1}, + {`first([])`, nil}, + {`first(1)`, "argument to `first` must be ARRAY, got INTEGER"}, + {`last([1, 2, 3])`, 3}, + {`last([])`, nil}, + {`last(1)`, "argument to `last` must be ARRAY, got INTEGER"}, + {`rest([1, 2, 3])`, []int{2, 3}}, + {`rest([])`, nil}, + {`push([], 1)`, []int{1}}, + {`push(1, 1)`, "argument to `push` must be ARRAY, got INTEGER"}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + + switch expected := tt.expected.(type) { + case int: + testIntegerObject(t, evaluated, int64(expected)) + case nil: + testNullObject(t, evaluated) + case string: + errObj, ok := evaluated.(*object.Error) + if !ok { + t.Errorf("object is not Error. got=%T (%+v)", + evaluated, evaluated) + continue + } + if errObj.Message != expected { + t.Errorf("wrong error message. expected=%q, got=%q", + expected, errObj.Message) + } + case []int: + array, ok := evaluated.(*object.Array) + if !ok { + t.Errorf("obj not Array. got=%T (%+v)", evaluated, evaluated) + continue + } + + if len(array.Elements) != len(expected) { + t.Errorf("wrong num of elements. want=%d, got=%d", + len(expected), len(array.Elements)) + continue + } + + for i, expectedElem := range expected { + testIntegerObject(t, array.Elements[i], int64(expectedElem)) + } + } + } +} + +func TestArrayLiterals(t *testing.T) { + input := "[1, 2 * 2, 3 + 3]" + + evaluated := testEval(input) + result, ok := evaluated.(*object.Array) + if !ok { + t.Fatalf("object is not Array. got=%T (%+v)", evaluated, evaluated) + } + + if len(result.Elements) != 3 { + t.Fatalf("array has wrong num of elements. got=%d", + len(result.Elements)) + } + + testIntegerObject(t, result.Elements[0], 1) + testIntegerObject(t, result.Elements[1], 4) + testIntegerObject(t, result.Elements[2], 6) +} + +func TestArrayIndexExpressions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + { + "[1, 2, 3][0]", + 1, + }, + { + "[1, 2, 3][1]", + 2, + }, + { + "[1, 2, 3][2]", + 3, + }, + { + "let i = 0; [1][i];", + 1, + }, + { + "[1, 2, 3][1 + 1];", + 3, + }, + { + "let myArray = [1, 2, 3]; myArray[2];", + 3, + }, + { + "let myArray = [1, 2, 3]; myArray[0] + myArray[1] + myArray[2];", + 6, + }, + { + "let myArray = [1, 2, 3]; let i = myArray[0]; myArray[i]", + 2, + }, + { + "[1, 2, 3][3]", + nil, + }, + { + "[1, 2, 3][-1]", + nil, + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + integer, ok := tt.expected.(int) + if ok { + testIntegerObject(t, evaluated, int64(integer)) + } else { + testNullObject(t, evaluated) + } + } +} + +func TestHashLiterals(t *testing.T) { + input := `let two = "two"; + { + "one": 10 - 9, + two: 1 + 1, + "thr" + "ee": 6 / 2, + 4: 4, + true: 5, + false: 6 + }` + + evaluated := testEval(input) + result, ok := evaluated.(*object.Hash) + if !ok { + t.Fatalf("Eval didn't return Hash. got=%T (%+v)", evaluated, evaluated) + } + + expected := map[object.HashKey]int64{ + (&object.String{Value: "one"}).HashKey(): 1, + (&object.String{Value: "two"}).HashKey(): 2, + (&object.String{Value: "three"}).HashKey(): 3, + (&object.Integer{Value: 4}).HashKey(): 4, + TRUE.HashKey(): 5, + FALSE.HashKey(): 6, + } + + if len(result.Pairs) != len(expected) { + t.Fatalf("Hash has wrong num of pairs. got=%d", len(result.Pairs)) + } + + for expectedKey, expectedValue := range expected { + pair, ok := result.Pairs[expectedKey] + if !ok { + t.Errorf("no pair for given key in Pairs") + } + + testIntegerObject(t, pair.Value, expectedValue) + } +} + +func TestHashIndexExpressions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + { + `{"foo": 5}["foo"]`, + 5, + }, + { + `{"foo": 5}["bar"]`, + nil, + }, + { + `let key = "foo"; {"foo": 5}[key]`, + 5, + }, + { + `{}["foo"]`, + nil, + }, + { + `{5: 5}[5]`, + 5, + }, + { + `{true: 5}[true]`, + 5, + }, + { + `{false: 5}[false]`, + 5, + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + integer, ok := tt.expected.(int) + if ok { + testIntegerObject(t, evaluated, int64(integer)) + } else { + testNullObject(t, evaluated) + } + } +} +func testEval(input string) object.Object { + l := lexer.New(input) + p := parser.New(l) + program := p.ParseProgram() + env := object.NewEnvironment() + + return Eval(program, env) +} + +func testIntegerObject(t *testing.T, obj object.Object, expected int64) bool { + result, ok := obj.(*object.Integer) + if !ok { + t.Errorf("object is not Integer. got=%T (%+v)", obj, obj) + return false + } + if result.Value != expected { + t.Errorf("object has wrong value. got=%d, want=%d", + result.Value, expected) + return false + } + + return true +} + +func testBooleanObject(t *testing.T, obj object.Object, expected bool) bool { + result, ok := obj.(*object.Boolean) + if !ok { + t.Errorf("object is not Boolean. got=%T (%+v)", obj, obj) + return false + } + if result.Value != expected { + t.Errorf("object has wrong value. got=%t, want=%t", + result.Value, expected) + return false + } + return true +} + +func testNullObject(t *testing.T, obj object.Object) bool { + if obj != NULL { + t.Errorf("object is not NULL. got=%T (%+v)", obj, obj) + return false + } + return true +} diff --git a/wcig_code_1_2/06/src/monkey/go.mod b/wcig_code_1_2/06/src/monkey/go.mod new file mode 100644 index 0000000..3064854 --- /dev/null +++ b/wcig_code_1_2/06/src/monkey/go.mod @@ -0,0 +1,3 @@ +module monkey + +go 1.14 diff --git a/wcig_code_1_2/06/src/monkey/lexer/lexer.go b/wcig_code_1_2/06/src/monkey/lexer/lexer.go new file mode 100644 index 0000000..db4f5f7 --- /dev/null +++ b/wcig_code_1_2/06/src/monkey/lexer/lexer.go @@ -0,0 +1,157 @@ +package lexer + +import "monkey/token" + +type Lexer struct { + input string + position int // current position in input (points to current char) + readPosition int // current reading position in input (after current char) + ch byte // current char under examination +} + +func New(input string) *Lexer { + l := &Lexer{input: input} + l.readChar() + return l +} + +func (l *Lexer) NextToken() token.Token { + var tok token.Token + + l.skipWhitespace() + + switch l.ch { + case '=': + if l.peekChar() == '=' { + ch := l.ch + l.readChar() + literal := string(ch) + string(l.ch) + tok = token.Token{Type: token.EQ, Literal: literal} + } else { + tok = newToken(token.ASSIGN, l.ch) + } + case '+': + tok = newToken(token.PLUS, l.ch) + case '-': + tok = newToken(token.MINUS, l.ch) + case '!': + if l.peekChar() == '=' { + ch := l.ch + l.readChar() + literal := string(ch) + string(l.ch) + tok = token.Token{Type: token.NOT_EQ, Literal: literal} + } else { + tok = newToken(token.BANG, l.ch) + } + case '/': + tok = newToken(token.SLASH, l.ch) + case '*': + tok = newToken(token.ASTERISK, l.ch) + case '<': + tok = newToken(token.LT, l.ch) + case '>': + tok = newToken(token.GT, l.ch) + case ';': + tok = newToken(token.SEMICOLON, l.ch) + case ':': + tok = newToken(token.COLON, l.ch) + case ',': + tok = newToken(token.COMMA, l.ch) + case '{': + tok = newToken(token.LBRACE, l.ch) + case '}': + tok = newToken(token.RBRACE, l.ch) + case '(': + tok = newToken(token.LPAREN, l.ch) + case ')': + tok = newToken(token.RPAREN, l.ch) + case '"': + tok.Type = token.STRING + tok.Literal = l.readString() + case '[': + tok = newToken(token.LBRACKET, l.ch) + case ']': + tok = newToken(token.RBRACKET, l.ch) + case 0: + tok.Literal = "" + tok.Type = token.EOF + default: + if isLetter(l.ch) { + tok.Literal = l.readIdentifier() + tok.Type = token.LookupIdent(tok.Literal) + return tok + } else if isDigit(l.ch) { + tok.Type = token.INT + tok.Literal = l.readNumber() + return tok + } else { + tok = newToken(token.ILLEGAL, l.ch) + } + } + + l.readChar() + return tok +} + +func (l *Lexer) skipWhitespace() { + for l.ch == ' ' || l.ch == '\t' || l.ch == '\n' || l.ch == '\r' { + l.readChar() + } +} + +func (l *Lexer) readChar() { + if l.readPosition >= len(l.input) { + l.ch = 0 + } else { + l.ch = l.input[l.readPosition] + } + l.position = l.readPosition + l.readPosition += 1 +} + +func (l *Lexer) peekChar() byte { + if l.readPosition >= len(l.input) { + return 0 + } else { + return l.input[l.readPosition] + } +} + +func (l *Lexer) readIdentifier() string { + position := l.position + for isLetter(l.ch) { + l.readChar() + } + return l.input[position:l.position] +} + +func (l *Lexer) readNumber() string { + position := l.position + for isDigit(l.ch) { + l.readChar() + } + return l.input[position:l.position] +} + +func (l *Lexer) readString() string { + position := l.position + 1 + for { + l.readChar() + if l.ch == '"' || l.ch == 0 { + break + } + } + return l.input[position:l.position] +} + +func isLetter(ch byte) bool { + return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' +} + +func isDigit(ch byte) bool { + return '0' <= ch && ch <= '9' +} + +func newToken(tokenType token.TokenType, ch byte) token.Token { + return token.Token{Type: tokenType, Literal: string(ch)} +} diff --git a/wcig_code_1_2/06/src/monkey/lexer/lexer_test.go b/wcig_code_1_2/06/src/monkey/lexer/lexer_test.go new file mode 100644 index 0000000..e4e64a4 --- /dev/null +++ b/wcig_code_1_2/06/src/monkey/lexer/lexer_test.go @@ -0,0 +1,143 @@ +package lexer + +import ( + "testing" + + "monkey/token" +) + +func TestNextToken(t *testing.T) { + input := `let five = 5; +let ten = 10; + +let add = fn(x, y) { + x + y; +}; + +let result = add(five, ten); +!-/*5; +5 < 10 > 5; + +if (5 < 10) { + return true; +} else { + return false; +} + +10 == 10; +10 != 9; +"foobar" +"foo bar" +[1, 2]; +{"foo": "bar"} +` + + tests := []struct { + expectedType token.TokenType + expectedLiteral string + }{ + {token.LET, "let"}, + {token.IDENT, "five"}, + {token.ASSIGN, "="}, + {token.INT, "5"}, + {token.SEMICOLON, ";"}, + {token.LET, "let"}, + {token.IDENT, "ten"}, + {token.ASSIGN, "="}, + {token.INT, "10"}, + {token.SEMICOLON, ";"}, + {token.LET, "let"}, + {token.IDENT, "add"}, + {token.ASSIGN, "="}, + {token.FUNCTION, "fn"}, + {token.LPAREN, "("}, + {token.IDENT, "x"}, + {token.COMMA, ","}, + {token.IDENT, "y"}, + {token.RPAREN, ")"}, + {token.LBRACE, "{"}, + {token.IDENT, "x"}, + {token.PLUS, "+"}, + {token.IDENT, "y"}, + {token.SEMICOLON, ";"}, + {token.RBRACE, "}"}, + {token.SEMICOLON, ";"}, + {token.LET, "let"}, + {token.IDENT, "result"}, + {token.ASSIGN, "="}, + {token.IDENT, "add"}, + {token.LPAREN, "("}, + {token.IDENT, "five"}, + {token.COMMA, ","}, + {token.IDENT, "ten"}, + {token.RPAREN, ")"}, + {token.SEMICOLON, ";"}, + {token.BANG, "!"}, + {token.MINUS, "-"}, + {token.SLASH, "/"}, + {token.ASTERISK, "*"}, + {token.INT, "5"}, + {token.SEMICOLON, ";"}, + {token.INT, "5"}, + {token.LT, "<"}, + {token.INT, "10"}, + {token.GT, ">"}, + {token.INT, "5"}, + {token.SEMICOLON, ";"}, + {token.IF, "if"}, + {token.LPAREN, "("}, + {token.INT, "5"}, + {token.LT, "<"}, + {token.INT, "10"}, + {token.RPAREN, ")"}, + {token.LBRACE, "{"}, + {token.RETURN, "return"}, + {token.TRUE, "true"}, + {token.SEMICOLON, ";"}, + {token.RBRACE, "}"}, + {token.ELSE, "else"}, + {token.LBRACE, "{"}, + {token.RETURN, "return"}, + {token.FALSE, "false"}, + {token.SEMICOLON, ";"}, + {token.RBRACE, "}"}, + {token.INT, "10"}, + {token.EQ, "=="}, + {token.INT, "10"}, + {token.SEMICOLON, ";"}, + {token.INT, "10"}, + {token.NOT_EQ, "!="}, + {token.INT, "9"}, + {token.SEMICOLON, ";"}, + {token.STRING, "foobar"}, + {token.STRING, "foo bar"}, + {token.LBRACKET, "["}, + {token.INT, "1"}, + {token.COMMA, ","}, + {token.INT, "2"}, + {token.RBRACKET, "]"}, + {token.SEMICOLON, ";"}, + {token.LBRACE, "{"}, + {token.STRING, "foo"}, + {token.COLON, ":"}, + {token.STRING, "bar"}, + {token.RBRACE, "}"}, + {token.EOF, ""}, + } + + l := New(input) + + for i, tt := range tests { + tok := l.NextToken() + + if tok.Type != tt.expectedType { + t.Fatalf("tests[%d] - tokentype wrong. expected=%q, got=%q", + i, tt.expectedType, tok.Type) + } + + if tok.Literal != tt.expectedLiteral { + t.Fatalf("tests[%d] - literal wrong. expected=%q, got=%q", + i, tt.expectedLiteral, tok.Literal) + } + } +} diff --git a/wcig_code_1_2/06/src/monkey/main.go b/wcig_code_1_2/06/src/monkey/main.go new file mode 100644 index 0000000..1d27138 --- /dev/null +++ b/wcig_code_1_2/06/src/monkey/main.go @@ -0,0 +1,19 @@ +package main + +import ( + "fmt" + "monkey/repl" + "os" + "os/user" +) + +func main() { + user, err := user.Current() + if err != nil { + panic(err) + } + fmt.Printf("Hello %s! This is the Monkey programming language!\n", + user.Username) + fmt.Printf("Feel free to type in commands\n") + repl.Start(os.Stdin, os.Stdout) +} diff --git a/wcig_code_1_2/06/src/monkey/object/environment.go b/wcig_code_1_2/06/src/monkey/object/environment.go new file mode 100644 index 0000000..6f31070 --- /dev/null +++ b/wcig_code_1_2/06/src/monkey/object/environment.go @@ -0,0 +1,30 @@ +package object + +func NewEnclosedEnvironment(outer *Environment) *Environment { + env := NewEnvironment() + env.outer = outer + return env +} + +func NewEnvironment() *Environment { + s := make(map[string]Object) + return &Environment{store: s, outer: nil} +} + +type Environment struct { + store map[string]Object + outer *Environment +} + +func (e *Environment) Get(name string) (Object, bool) { + obj, ok := e.store[name] + if !ok && e.outer != nil { + obj, ok = e.outer.Get(name) + } + return obj, ok +} + +func (e *Environment) Set(name string, val Object) Object { + e.store[name] = val + return val +} diff --git a/wcig_code_1_2/06/src/monkey/object/object.go b/wcig_code_1_2/06/src/monkey/object/object.go new file mode 100644 index 0000000..2c2a1b0 --- /dev/null +++ b/wcig_code_1_2/06/src/monkey/object/object.go @@ -0,0 +1,182 @@ +package object + +import ( + "bytes" + "fmt" + "hash/fnv" + "monkey/ast" + "strings" +) + +type BuiltinFunction func(args ...Object) Object + +type ObjectType string + +const ( + NULL_OBJ = "NULL" + ERROR_OBJ = "ERROR" + + INTEGER_OBJ = "INTEGER" + BOOLEAN_OBJ = "BOOLEAN" + STRING_OBJ = "STRING" + + RETURN_VALUE_OBJ = "RETURN_VALUE" + + FUNCTION_OBJ = "FUNCTION" + BUILTIN_OBJ = "BUILTIN" + + ARRAY_OBJ = "ARRAY" + HASH_OBJ = "HASH" +) + +type HashKey struct { + Type ObjectType + Value uint64 +} + +type Hashable interface { + HashKey() HashKey +} + +type Object interface { + Type() ObjectType + Inspect() string +} + +type Integer struct { + Value int64 +} + +func (i *Integer) Type() ObjectType { return INTEGER_OBJ } +func (i *Integer) Inspect() string { return fmt.Sprintf("%d", i.Value) } +func (i *Integer) HashKey() HashKey { + return HashKey{Type: i.Type(), Value: uint64(i.Value)} +} + +type Boolean struct { + Value bool +} + +func (b *Boolean) Type() ObjectType { return BOOLEAN_OBJ } +func (b *Boolean) Inspect() string { return fmt.Sprintf("%t", b.Value) } +func (b *Boolean) HashKey() HashKey { + var value uint64 + + if b.Value { + value = 1 + } else { + value = 0 + } + + return HashKey{Type: b.Type(), Value: value} +} + +type Null struct{} + +func (n *Null) Type() ObjectType { return NULL_OBJ } +func (n *Null) Inspect() string { return "null" } + +type ReturnValue struct { + Value Object +} + +func (rv *ReturnValue) Type() ObjectType { return RETURN_VALUE_OBJ } +func (rv *ReturnValue) Inspect() string { return rv.Value.Inspect() } + +type Error struct { + Message string +} + +func (e *Error) Type() ObjectType { return ERROR_OBJ } +func (e *Error) Inspect() string { return "ERROR: " + e.Message } + +type Function struct { + Parameters []*ast.Identifier + Body *ast.BlockStatement + Env *Environment +} + +func (f *Function) Type() ObjectType { return FUNCTION_OBJ } +func (f *Function) Inspect() string { + var out bytes.Buffer + + params := []string{} + for _, p := range f.Parameters { + params = append(params, p.String()) + } + + out.WriteString("fn") + out.WriteString("(") + out.WriteString(strings.Join(params, ", ")) + out.WriteString(") {\n") + out.WriteString(f.Body.String()) + out.WriteString("\n}") + + return out.String() +} + +type String struct { + Value string +} + +func (s *String) Type() ObjectType { return STRING_OBJ } +func (s *String) Inspect() string { return s.Value } +func (s *String) HashKey() HashKey { + h := fnv.New64a() + h.Write([]byte(s.Value)) + + return HashKey{Type: s.Type(), Value: h.Sum64()} +} + +type Builtin struct { + Fn BuiltinFunction +} + +func (b *Builtin) Type() ObjectType { return BUILTIN_OBJ } +func (b *Builtin) Inspect() string { return "builtin function" } + +type Array struct { + Elements []Object +} + +func (ao *Array) Type() ObjectType { return ARRAY_OBJ } +func (ao *Array) Inspect() string { + var out bytes.Buffer + + elements := []string{} + for _, e := range ao.Elements { + elements = append(elements, e.Inspect()) + } + + out.WriteString("[") + out.WriteString(strings.Join(elements, ", ")) + out.WriteString("]") + + return out.String() +} + +type HashPair struct { + Key Object + Value Object +} + +type Hash struct { + Pairs map[HashKey]HashPair +} + +func (h *Hash) Type() ObjectType { return HASH_OBJ } +func (h *Hash) Inspect() string { + var out bytes.Buffer + + pairs := []string{} + for _, pair := range h.Pairs { + pairs = append(pairs, fmt.Sprintf("%s: %s", + pair.Key.Inspect(), pair.Value.Inspect())) + } + + out.WriteString("{") + out.WriteString(strings.Join(pairs, ", ")) + out.WriteString("}") + + return out.String() +} diff --git a/wcig_code_1_2/06/src/monkey/object/object_test.go b/wcig_code_1_2/06/src/monkey/object/object_test.go new file mode 100644 index 0000000..63228a2 --- /dev/null +++ b/wcig_code_1_2/06/src/monkey/object/object_test.go @@ -0,0 +1,60 @@ +package object + +import "testing" + +func TestStringHashKey(t *testing.T) { + hello1 := &String{Value: "Hello World"} + hello2 := &String{Value: "Hello World"} + diff1 := &String{Value: "My name is johnny"} + diff2 := &String{Value: "My name is johnny"} + + if hello1.HashKey() != hello2.HashKey() { + t.Errorf("strings with same content have different hash keys") + } + + if diff1.HashKey() != diff2.HashKey() { + t.Errorf("strings with same content have different hash keys") + } + + if hello1.HashKey() == diff1.HashKey() { + t.Errorf("strings with different content have same hash keys") + } +} + +func TestBooleanHashKey(t *testing.T) { + true1 := &Boolean{Value: true} + true2 := &Boolean{Value: true} + false1 := &Boolean{Value: false} + false2 := &Boolean{Value: false} + + if true1.HashKey() != true2.HashKey() { + t.Errorf("trues do not have same hash key") + } + + if false1.HashKey() != false2.HashKey() { + t.Errorf("falses do not have same hash key") + } + + if true1.HashKey() == false1.HashKey() { + t.Errorf("true has same hash key as false") + } +} + +func TestIntegerHashKey(t *testing.T) { + one1 := &Integer{Value: 1} + one2 := &Integer{Value: 1} + two1 := &Integer{Value: 2} + two2 := &Integer{Value: 2} + + if one1.HashKey() != one2.HashKey() { + t.Errorf("integers with same content have twoerent hash keys") + } + + if two1.HashKey() != two2.HashKey() { + t.Errorf("integers with same content have twoerent hash keys") + } + + if one1.HashKey() == two1.HashKey() { + t.Errorf("integers with twoerent content have same hash keys") + } +} diff --git a/wcig_code_1_2/06/src/monkey/parser/parser.go b/wcig_code_1_2/06/src/monkey/parser/parser.go new file mode 100644 index 0000000..0a1f19c --- /dev/null +++ b/wcig_code_1_2/06/src/monkey/parser/parser.go @@ -0,0 +1,491 @@ +package parser + +import ( + "fmt" + "monkey/ast" + "monkey/lexer" + "monkey/token" + "strconv" +) + +const ( + _ int = iota + LOWEST + EQUALS // == + LESSGREATER // > or < + SUM // + + PRODUCT // * + PREFIX // -X or !X + CALL // myFunction(X) + INDEX // array[index] +) + +var precedences = map[token.TokenType]int{ + token.EQ: EQUALS, + token.NOT_EQ: EQUALS, + token.LT: LESSGREATER, + token.GT: LESSGREATER, + token.PLUS: SUM, + token.MINUS: SUM, + token.SLASH: PRODUCT, + token.ASTERISK: PRODUCT, + token.LPAREN: CALL, + token.LBRACKET: INDEX, +} + +type ( + prefixParseFn func() ast.Expression + infixParseFn func(ast.Expression) ast.Expression +) + +type Parser struct { + l *lexer.Lexer + errors []string + + curToken token.Token + peekToken token.Token + + prefixParseFns map[token.TokenType]prefixParseFn + infixParseFns map[token.TokenType]infixParseFn +} + +func New(l *lexer.Lexer) *Parser { + p := &Parser{ + l: l, + errors: []string{}, + } + + p.prefixParseFns = make(map[token.TokenType]prefixParseFn) + p.registerPrefix(token.IDENT, p.parseIdentifier) + p.registerPrefix(token.INT, p.parseIntegerLiteral) + p.registerPrefix(token.STRING, p.parseStringLiteral) + p.registerPrefix(token.BANG, p.parsePrefixExpression) + p.registerPrefix(token.MINUS, p.parsePrefixExpression) + p.registerPrefix(token.TRUE, p.parseBoolean) + p.registerPrefix(token.FALSE, p.parseBoolean) + p.registerPrefix(token.LPAREN, p.parseGroupedExpression) + p.registerPrefix(token.IF, p.parseIfExpression) + p.registerPrefix(token.FUNCTION, p.parseFunctionLiteral) + p.registerPrefix(token.LBRACKET, p.parseArrayLiteral) + p.registerPrefix(token.LBRACE, p.parseHashLiteral) + + p.infixParseFns = make(map[token.TokenType]infixParseFn) + p.registerInfix(token.PLUS, p.parseInfixExpression) + p.registerInfix(token.MINUS, p.parseInfixExpression) + p.registerInfix(token.SLASH, p.parseInfixExpression) + p.registerInfix(token.ASTERISK, p.parseInfixExpression) + p.registerInfix(token.EQ, p.parseInfixExpression) + p.registerInfix(token.NOT_EQ, p.parseInfixExpression) + p.registerInfix(token.LT, p.parseInfixExpression) + p.registerInfix(token.GT, p.parseInfixExpression) + + p.registerInfix(token.LPAREN, p.parseCallExpression) + p.registerInfix(token.LBRACKET, p.parseIndexExpression) + + // Read two tokens, so curToken and peekToken are both set + p.nextToken() + p.nextToken() + + return p +} + +func (p *Parser) nextToken() { + p.curToken = p.peekToken + p.peekToken = p.l.NextToken() +} + +func (p *Parser) curTokenIs(t token.TokenType) bool { + return p.curToken.Type == t +} + +func (p *Parser) peekTokenIs(t token.TokenType) bool { + return p.peekToken.Type == t +} + +func (p *Parser) expectPeek(t token.TokenType) bool { + if p.peekTokenIs(t) { + p.nextToken() + return true + } else { + p.peekError(t) + return false + } +} + +func (p *Parser) Errors() []string { + return p.errors +} + +func (p *Parser) peekError(t token.TokenType) { + msg := fmt.Sprintf("expected next token to be %s, got %s instead", + t, p.peekToken.Type) + p.errors = append(p.errors, msg) +} + +func (p *Parser) noPrefixParseFnError(t token.TokenType) { + msg := fmt.Sprintf("no prefix parse function for %s found", t) + p.errors = append(p.errors, msg) +} + +func (p *Parser) ParseProgram() *ast.Program { + program := &ast.Program{} + program.Statements = []ast.Statement{} + + for !p.curTokenIs(token.EOF) { + stmt := p.parseStatement() + if stmt != nil { + program.Statements = append(program.Statements, stmt) + } + p.nextToken() + } + + return program +} + +func (p *Parser) parseStatement() ast.Statement { + switch p.curToken.Type { + case token.LET: + return p.parseLetStatement() + case token.RETURN: + return p.parseReturnStatement() + default: + return p.parseExpressionStatement() + } +} + +func (p *Parser) parseLetStatement() *ast.LetStatement { + stmt := &ast.LetStatement{Token: p.curToken} + + if !p.expectPeek(token.IDENT) { + return nil + } + + stmt.Name = &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + + if !p.expectPeek(token.ASSIGN) { + return nil + } + + p.nextToken() + + stmt.Value = p.parseExpression(LOWEST) + + if p.peekTokenIs(token.SEMICOLON) { + p.nextToken() + } + + return stmt +} + +func (p *Parser) parseReturnStatement() *ast.ReturnStatement { + stmt := &ast.ReturnStatement{Token: p.curToken} + + p.nextToken() + + stmt.ReturnValue = p.parseExpression(LOWEST) + + if p.peekTokenIs(token.SEMICOLON) { + p.nextToken() + } + + return stmt +} + +func (p *Parser) parseExpressionStatement() *ast.ExpressionStatement { + stmt := &ast.ExpressionStatement{Token: p.curToken} + + stmt.Expression = p.parseExpression(LOWEST) + + if p.peekTokenIs(token.SEMICOLON) { + p.nextToken() + } + + return stmt +} + +func (p *Parser) parseExpression(precedence int) ast.Expression { + prefix := p.prefixParseFns[p.curToken.Type] + if prefix == nil { + p.noPrefixParseFnError(p.curToken.Type) + return nil + } + leftExp := prefix() + + for !p.peekTokenIs(token.SEMICOLON) && precedence < p.peekPrecedence() { + infix := p.infixParseFns[p.peekToken.Type] + if infix == nil { + return leftExp + } + + p.nextToken() + + leftExp = infix(leftExp) + } + + return leftExp +} + +func (p *Parser) peekPrecedence() int { + if p, ok := precedences[p.peekToken.Type]; ok { + return p + } + + return LOWEST +} + +func (p *Parser) curPrecedence() int { + if p, ok := precedences[p.curToken.Type]; ok { + return p + } + + return LOWEST +} + +func (p *Parser) parseIdentifier() ast.Expression { + return &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} +} + +func (p *Parser) parseIntegerLiteral() ast.Expression { + lit := &ast.IntegerLiteral{Token: p.curToken} + + value, err := strconv.ParseInt(p.curToken.Literal, 0, 64) + if err != nil { + msg := fmt.Sprintf("could not parse %q as integer", p.curToken.Literal) + p.errors = append(p.errors, msg) + return nil + } + + lit.Value = value + + return lit +} + +func (p *Parser) parseStringLiteral() ast.Expression { + return &ast.StringLiteral{Token: p.curToken, Value: p.curToken.Literal} +} + +func (p *Parser) parsePrefixExpression() ast.Expression { + expression := &ast.PrefixExpression{ + Token: p.curToken, + Operator: p.curToken.Literal, + } + + p.nextToken() + + expression.Right = p.parseExpression(PREFIX) + + return expression +} + +func (p *Parser) parseInfixExpression(left ast.Expression) ast.Expression { + expression := &ast.InfixExpression{ + Token: p.curToken, + Operator: p.curToken.Literal, + Left: left, + } + + precedence := p.curPrecedence() + p.nextToken() + expression.Right = p.parseExpression(precedence) + + return expression +} + +func (p *Parser) parseBoolean() ast.Expression { + return &ast.Boolean{Token: p.curToken, Value: p.curTokenIs(token.TRUE)} +} + +func (p *Parser) parseGroupedExpression() ast.Expression { + p.nextToken() + + exp := p.parseExpression(LOWEST) + + if !p.expectPeek(token.RPAREN) { + return nil + } + + return exp +} + +func (p *Parser) parseIfExpression() ast.Expression { + expression := &ast.IfExpression{Token: p.curToken} + + if !p.expectPeek(token.LPAREN) { + return nil + } + + p.nextToken() + expression.Condition = p.parseExpression(LOWEST) + + if !p.expectPeek(token.RPAREN) { + return nil + } + + if !p.expectPeek(token.LBRACE) { + return nil + } + + expression.Consequence = p.parseBlockStatement() + + if p.peekTokenIs(token.ELSE) { + p.nextToken() + + if !p.expectPeek(token.LBRACE) { + return nil + } + + expression.Alternative = p.parseBlockStatement() + } + + return expression +} + +func (p *Parser) parseBlockStatement() *ast.BlockStatement { + block := &ast.BlockStatement{Token: p.curToken} + block.Statements = []ast.Statement{} + + p.nextToken() + + for !p.curTokenIs(token.RBRACE) && !p.curTokenIs(token.EOF) { + stmt := p.parseStatement() + if stmt != nil { + block.Statements = append(block.Statements, stmt) + } + p.nextToken() + } + + return block +} + +func (p *Parser) parseFunctionLiteral() ast.Expression { + lit := &ast.FunctionLiteral{Token: p.curToken} + + if !p.expectPeek(token.LPAREN) { + return nil + } + + lit.Parameters = p.parseFunctionParameters() + + if !p.expectPeek(token.LBRACE) { + return nil + } + + lit.Body = p.parseBlockStatement() + + return lit +} + +func (p *Parser) parseFunctionParameters() []*ast.Identifier { + identifiers := []*ast.Identifier{} + + if p.peekTokenIs(token.RPAREN) { + p.nextToken() + return identifiers + } + + p.nextToken() + + ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + identifiers = append(identifiers, ident) + + for p.peekTokenIs(token.COMMA) { + p.nextToken() + p.nextToken() + ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + identifiers = append(identifiers, ident) + } + + if !p.expectPeek(token.RPAREN) { + return nil + } + + return identifiers +} + +func (p *Parser) parseCallExpression(function ast.Expression) ast.Expression { + exp := &ast.CallExpression{Token: p.curToken, Function: function} + exp.Arguments = p.parseExpressionList(token.RPAREN) + return exp +} + +func (p *Parser) parseExpressionList(end token.TokenType) []ast.Expression { + list := []ast.Expression{} + + if p.peekTokenIs(end) { + p.nextToken() + return list + } + + p.nextToken() + list = append(list, p.parseExpression(LOWEST)) + + for p.peekTokenIs(token.COMMA) { + p.nextToken() + p.nextToken() + list = append(list, p.parseExpression(LOWEST)) + } + + if !p.expectPeek(end) { + return nil + } + + return list +} + +func (p *Parser) parseArrayLiteral() ast.Expression { + array := &ast.ArrayLiteral{Token: p.curToken} + + array.Elements = p.parseExpressionList(token.RBRACKET) + + return array +} + +func (p *Parser) parseIndexExpression(left ast.Expression) ast.Expression { + exp := &ast.IndexExpression{Token: p.curToken, Left: left} + + p.nextToken() + exp.Index = p.parseExpression(LOWEST) + + if !p.expectPeek(token.RBRACKET) { + return nil + } + + return exp +} + +func (p *Parser) parseHashLiteral() ast.Expression { + hash := &ast.HashLiteral{Token: p.curToken} + hash.Pairs = make(map[ast.Expression]ast.Expression) + + for !p.peekTokenIs(token.RBRACE) { + p.nextToken() + key := p.parseExpression(LOWEST) + + if !p.expectPeek(token.COLON) { + return nil + } + + p.nextToken() + value := p.parseExpression(LOWEST) + + hash.Pairs[key] = value + + if !p.peekTokenIs(token.RBRACE) && !p.expectPeek(token.COMMA) { + return nil + } + } + + if !p.expectPeek(token.RBRACE) { + return nil + } + + return hash +} + +func (p *Parser) registerPrefix(tokenType token.TokenType, fn prefixParseFn) { + p.prefixParseFns[tokenType] = fn +} + +func (p *Parser) registerInfix(tokenType token.TokenType, fn infixParseFn) { + p.infixParseFns[tokenType] = fn +} diff --git a/wcig_code_1_2/06/src/monkey/parser/parser_test.go b/wcig_code_1_2/06/src/monkey/parser/parser_test.go new file mode 100644 index 0000000..674487d --- /dev/null +++ b/wcig_code_1_2/06/src/monkey/parser/parser_test.go @@ -0,0 +1,1083 @@ +package parser + +import ( + "fmt" + "monkey/ast" + "monkey/lexer" + "testing" +) + +func TestLetStatements(t *testing.T) { + tests := []struct { + input string + expectedIdentifier string + expectedValue interface{} + }{ + {"let x = 5;", "x", 5}, + {"let y = true;", "y", true}, + {"let foobar = y;", "foobar", "y"}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain 1 statements. got=%d", + len(program.Statements)) + } + + stmt := program.Statements[0] + if !testLetStatement(t, stmt, tt.expectedIdentifier) { + return + } + + val := stmt.(*ast.LetStatement).Value + if !testLiteralExpression(t, val, tt.expectedValue) { + return + } + } +} + +func TestReturnStatements(t *testing.T) { + tests := []struct { + input string + expectedValue interface{} + }{ + {"return 5;", 5}, + {"return true;", true}, + {"return foobar;", "foobar"}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain 1 statements. got=%d", + len(program.Statements)) + } + + stmt := program.Statements[0] + returnStmt, ok := stmt.(*ast.ReturnStatement) + if !ok { + t.Fatalf("stmt not *ast.returnStatement. got=%T", stmt) + } + if returnStmt.TokenLiteral() != "return" { + t.Fatalf("returnStmt.TokenLiteral not 'return', got %q", + returnStmt.TokenLiteral()) + } + if testLiteralExpression(t, returnStmt.ReturnValue, tt.expectedValue) { + return + } + } +} + +func TestIdentifierExpression(t *testing.T) { + input := "foobar;" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program has not enough statements. got=%d", + len(program.Statements)) + } + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + ident, ok := stmt.Expression.(*ast.Identifier) + if !ok { + t.Fatalf("exp not *ast.Identifier. got=%T", stmt.Expression) + } + if ident.Value != "foobar" { + t.Errorf("ident.Value not %s. got=%s", "foobar", ident.Value) + } + if ident.TokenLiteral() != "foobar" { + t.Errorf("ident.TokenLiteral not %s. got=%s", "foobar", + ident.TokenLiteral()) + } +} + +func TestIntegerLiteralExpression(t *testing.T) { + input := "5;" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program has not enough statements. got=%d", + len(program.Statements)) + } + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + literal, ok := stmt.Expression.(*ast.IntegerLiteral) + if !ok { + t.Fatalf("exp not *ast.IntegerLiteral. got=%T", stmt.Expression) + } + if literal.Value != 5 { + t.Errorf("literal.Value not %d. got=%d", 5, literal.Value) + } + if literal.TokenLiteral() != "5" { + t.Errorf("literal.TokenLiteral not %s. got=%s", "5", + literal.TokenLiteral()) + } +} + +func TestParsingPrefixExpressions(t *testing.T) { + prefixTests := []struct { + input string + operator string + value interface{} + }{ + {"!5;", "!", 5}, + {"-15;", "-", 15}, + {"!foobar;", "!", "foobar"}, + {"-foobar;", "-", "foobar"}, + {"!true;", "!", true}, + {"!false;", "!", false}, + } + + for _, tt := range prefixTests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.PrefixExpression) + if !ok { + t.Fatalf("stmt is not ast.PrefixExpression. got=%T", stmt.Expression) + } + if exp.Operator != tt.operator { + t.Fatalf("exp.Operator is not '%s'. got=%s", + tt.operator, exp.Operator) + } + if !testLiteralExpression(t, exp.Right, tt.value) { + return + } + } +} + +func TestParsingInfixExpressions(t *testing.T) { + infixTests := []struct { + input string + leftValue interface{} + operator string + rightValue interface{} + }{ + {"5 + 5;", 5, "+", 5}, + {"5 - 5;", 5, "-", 5}, + {"5 * 5;", 5, "*", 5}, + {"5 / 5;", 5, "/", 5}, + {"5 > 5;", 5, ">", 5}, + {"5 < 5;", 5, "<", 5}, + {"5 == 5;", 5, "==", 5}, + {"5 != 5;", 5, "!=", 5}, + {"foobar + barfoo;", "foobar", "+", "barfoo"}, + {"foobar - barfoo;", "foobar", "-", "barfoo"}, + {"foobar * barfoo;", "foobar", "*", "barfoo"}, + {"foobar / barfoo;", "foobar", "/", "barfoo"}, + {"foobar > barfoo;", "foobar", ">", "barfoo"}, + {"foobar < barfoo;", "foobar", "<", "barfoo"}, + {"foobar == barfoo;", "foobar", "==", "barfoo"}, + {"foobar != barfoo;", "foobar", "!=", "barfoo"}, + {"true == true", true, "==", true}, + {"true != false", true, "!=", false}, + {"false == false", false, "==", false}, + } + + for _, tt := range infixTests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + if !testInfixExpression(t, stmt.Expression, tt.leftValue, + tt.operator, tt.rightValue) { + return + } + } +} + +func TestOperatorPrecedenceParsing(t *testing.T) { + tests := []struct { + input string + expected string + }{ + { + "-a * b", + "((-a) * b)", + }, + { + "!-a", + "(!(-a))", + }, + { + "a + b + c", + "((a + b) + c)", + }, + { + "a + b - c", + "((a + b) - c)", + }, + { + "a * b * c", + "((a * b) * c)", + }, + { + "a * b / c", + "((a * b) / c)", + }, + { + "a + b / c", + "(a + (b / c))", + }, + { + "a + b * c + d / e - f", + "(((a + (b * c)) + (d / e)) - f)", + }, + { + "3 + 4; -5 * 5", + "(3 + 4)((-5) * 5)", + }, + { + "5 > 4 == 3 < 4", + "((5 > 4) == (3 < 4))", + }, + { + "5 < 4 != 3 > 4", + "((5 < 4) != (3 > 4))", + }, + { + "3 + 4 * 5 == 3 * 1 + 4 * 5", + "((3 + (4 * 5)) == ((3 * 1) + (4 * 5)))", + }, + { + "true", + "true", + }, + { + "false", + "false", + }, + { + "3 > 5 == false", + "((3 > 5) == false)", + }, + { + "3 < 5 == true", + "((3 < 5) == true)", + }, + { + "1 + (2 + 3) + 4", + "((1 + (2 + 3)) + 4)", + }, + { + "(5 + 5) * 2", + "((5 + 5) * 2)", + }, + { + "2 / (5 + 5)", + "(2 / (5 + 5))", + }, + { + "(5 + 5) * 2 * (5 + 5)", + "(((5 + 5) * 2) * (5 + 5))", + }, + { + "-(5 + 5)", + "(-(5 + 5))", + }, + { + "!(true == true)", + "(!(true == true))", + }, + { + "a + add(b * c) + d", + "((a + add((b * c))) + d)", + }, + { + "add(a, b, 1, 2 * 3, 4 + 5, add(6, 7 * 8))", + "add(a, b, 1, (2 * 3), (4 + 5), add(6, (7 * 8)))", + }, + { + "add(a + b + c * d / f + g)", + "add((((a + b) + ((c * d) / f)) + g))", + }, + { + "a * [1, 2, 3, 4][b * c] * d", + "((a * ([1, 2, 3, 4][(b * c)])) * d)", + }, + { + "add(a * b[2], b[1], 2 * [1, 2][1])", + "add((a * (b[2])), (b[1]), (2 * ([1, 2][1])))", + }, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + actual := program.String() + if actual != tt.expected { + t.Errorf("expected=%q, got=%q", tt.expected, actual) + } + } +} + +func TestBooleanExpression(t *testing.T) { + tests := []struct { + input string + expectedBoolean bool + }{ + {"true;", true}, + {"false;", false}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program has not enough statements. got=%d", + len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + boolean, ok := stmt.Expression.(*ast.Boolean) + if !ok { + t.Fatalf("exp not *ast.Boolean. got=%T", stmt.Expression) + } + if boolean.Value != tt.expectedBoolean { + t.Errorf("boolean.Value not %t. got=%t", tt.expectedBoolean, + boolean.Value) + } + } +} + +func TestIfExpression(t *testing.T) { + input := `if (x < y) { x }` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.IfExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.IfExpression. got=%T", + stmt.Expression) + } + + if !testInfixExpression(t, exp.Condition, "x", "<", "y") { + return + } + + if len(exp.Consequence.Statements) != 1 { + t.Errorf("consequence is not 1 statements. got=%d\n", + len(exp.Consequence.Statements)) + } + + consequence, ok := exp.Consequence.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", + exp.Consequence.Statements[0]) + } + + if !testIdentifier(t, consequence.Expression, "x") { + return + } + + if exp.Alternative != nil { + t.Errorf("exp.Alternative.Statements was not nil. got=%+v", exp.Alternative) + } +} + +func TestIfElseExpression(t *testing.T) { + input := `if (x < y) { x } else { y }` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.IfExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.IfExpression. got=%T", stmt.Expression) + } + + if !testInfixExpression(t, exp.Condition, "x", "<", "y") { + return + } + + if len(exp.Consequence.Statements) != 1 { + t.Errorf("consequence is not 1 statements. got=%d\n", + len(exp.Consequence.Statements)) + } + + consequence, ok := exp.Consequence.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", + exp.Consequence.Statements[0]) + } + + if !testIdentifier(t, consequence.Expression, "x") { + return + } + + if len(exp.Alternative.Statements) != 1 { + t.Errorf("exp.Alternative.Statements does not contain 1 statements. got=%d\n", + len(exp.Alternative.Statements)) + } + + alternative, ok := exp.Alternative.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", + exp.Alternative.Statements[0]) + } + + if !testIdentifier(t, alternative.Expression, "y") { + return + } +} + +func TestFunctionLiteralParsing(t *testing.T) { + input := `fn(x, y) { x + y; }` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + function, ok := stmt.Expression.(*ast.FunctionLiteral) + if !ok { + t.Fatalf("stmt.Expression is not ast.FunctionLiteral. got=%T", + stmt.Expression) + } + + if len(function.Parameters) != 2 { + t.Fatalf("function literal parameters wrong. want 2, got=%d\n", + len(function.Parameters)) + } + + testLiteralExpression(t, function.Parameters[0], "x") + testLiteralExpression(t, function.Parameters[1], "y") + + if len(function.Body.Statements) != 1 { + t.Fatalf("function.Body.Statements has not 1 statements. got=%d\n", + len(function.Body.Statements)) + } + + bodyStmt, ok := function.Body.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("function body stmt is not ast.ExpressionStatement. got=%T", + function.Body.Statements[0]) + } + + testInfixExpression(t, bodyStmt.Expression, "x", "+", "y") +} + +func TestFunctionParameterParsing(t *testing.T) { + tests := []struct { + input string + expectedParams []string + }{ + {input: "fn() {};", expectedParams: []string{}}, + {input: "fn(x) {};", expectedParams: []string{"x"}}, + {input: "fn(x, y, z) {};", expectedParams: []string{"x", "y", "z"}}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + function := stmt.Expression.(*ast.FunctionLiteral) + + if len(function.Parameters) != len(tt.expectedParams) { + t.Errorf("length parameters wrong. want %d, got=%d\n", + len(tt.expectedParams), len(function.Parameters)) + } + + for i, ident := range tt.expectedParams { + testLiteralExpression(t, function.Parameters[i], ident) + } + } +} + +func TestCallExpressionParsing(t *testing.T) { + input := "add(1, 2 * 3, 4 + 5);" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("stmt is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.CallExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.CallExpression. got=%T", + stmt.Expression) + } + + if !testIdentifier(t, exp.Function, "add") { + return + } + + if len(exp.Arguments) != 3 { + t.Fatalf("wrong length of arguments. got=%d", len(exp.Arguments)) + } + + testLiteralExpression(t, exp.Arguments[0], 1) + testInfixExpression(t, exp.Arguments[1], 2, "*", 3) + testInfixExpression(t, exp.Arguments[2], 4, "+", 5) +} + +func TestCallExpressionParameterParsing(t *testing.T) { + tests := []struct { + input string + expectedIdent string + expectedArgs []string + }{ + { + input: "add();", + expectedIdent: "add", + expectedArgs: []string{}, + }, + { + input: "add(1);", + expectedIdent: "add", + expectedArgs: []string{"1"}, + }, + { + input: "add(1, 2 * 3, 4 + 5);", + expectedIdent: "add", + expectedArgs: []string{"1", "(2 * 3)", "(4 + 5)"}, + }, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + exp, ok := stmt.Expression.(*ast.CallExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.CallExpression. got=%T", + stmt.Expression) + } + + if !testIdentifier(t, exp.Function, tt.expectedIdent) { + return + } + + if len(exp.Arguments) != len(tt.expectedArgs) { + t.Fatalf("wrong number of arguments. want=%d, got=%d", + len(tt.expectedArgs), len(exp.Arguments)) + } + + for i, arg := range tt.expectedArgs { + if exp.Arguments[i].String() != arg { + t.Errorf("argument %d wrong. want=%q, got=%q", i, + arg, exp.Arguments[i].String()) + } + } + } +} + +func TestStringLiteralExpression(t *testing.T) { + input := `"hello world";` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + literal, ok := stmt.Expression.(*ast.StringLiteral) + if !ok { + t.Fatalf("exp not *ast.StringLiteral. got=%T", stmt.Expression) + } + + if literal.Value != "hello world" { + t.Errorf("literal.Value not %q. got=%q", "hello world", literal.Value) + } +} + +func TestParsingEmptyArrayLiterals(t *testing.T) { + input := "[]" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + array, ok := stmt.Expression.(*ast.ArrayLiteral) + if !ok { + t.Fatalf("exp not ast.ArrayLiteral. got=%T", stmt.Expression) + } + + if len(array.Elements) != 0 { + t.Errorf("len(array.Elements) not 0. got=%d", len(array.Elements)) + } +} + +func TestParsingArrayLiterals(t *testing.T) { + input := "[1, 2 * 2, 3 + 3]" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + array, ok := stmt.Expression.(*ast.ArrayLiteral) + if !ok { + t.Fatalf("exp not ast.ArrayLiteral. got=%T", stmt.Expression) + } + + if len(array.Elements) != 3 { + t.Fatalf("len(array.Elements) not 3. got=%d", len(array.Elements)) + } + + testIntegerLiteral(t, array.Elements[0], 1) + testInfixExpression(t, array.Elements[1], 2, "*", 2) + testInfixExpression(t, array.Elements[2], 3, "+", 3) +} + +func TestParsingIndexExpressions(t *testing.T) { + input := "myArray[1 + 1]" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + indexExp, ok := stmt.Expression.(*ast.IndexExpression) + if !ok { + t.Fatalf("exp not *ast.IndexExpression. got=%T", stmt.Expression) + } + + if !testIdentifier(t, indexExp.Left, "myArray") { + return + } + + if !testInfixExpression(t, indexExp.Index, 1, "+", 1) { + return + } +} + +func TestParsingEmptyHashLiteral(t *testing.T) { + input := "{}" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + if len(hash.Pairs) != 0 { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } +} + +func TestParsingHashLiteralsStringKeys(t *testing.T) { + input := `{"one": 1, "two": 2, "three": 3}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + expected := map[string]int64{ + "one": 1, + "two": 2, + "three": 3, + } + + if len(hash.Pairs) != len(expected) { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } + + for key, value := range hash.Pairs { + literal, ok := key.(*ast.StringLiteral) + if !ok { + t.Errorf("key is not ast.StringLiteral. got=%T", key) + continue + } + + expectedValue := expected[literal.String()] + testIntegerLiteral(t, value, expectedValue) + } +} + +func TestParsingHashLiteralsBooleanKeys(t *testing.T) { + input := `{true: 1, false: 2}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + expected := map[string]int64{ + "true": 1, + "false": 2, + } + + if len(hash.Pairs) != len(expected) { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } + + for key, value := range hash.Pairs { + boolean, ok := key.(*ast.Boolean) + if !ok { + t.Errorf("key is not ast.BooleanLiteral. got=%T", key) + continue + } + + expectedValue := expected[boolean.String()] + testIntegerLiteral(t, value, expectedValue) + } +} + +func TestParsingHashLiteralsIntegerKeys(t *testing.T) { + input := `{1: 1, 2: 2, 3: 3}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + expected := map[string]int64{ + "1": 1, + "2": 2, + "3": 3, + } + + if len(hash.Pairs) != len(expected) { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } + + for key, value := range hash.Pairs { + integer, ok := key.(*ast.IntegerLiteral) + if !ok { + t.Errorf("key is not ast.IntegerLiteral. got=%T", key) + continue + } + + expectedValue := expected[integer.String()] + + testIntegerLiteral(t, value, expectedValue) + } +} + +func TestParsingHashLiteralsWithExpressions(t *testing.T) { + input := `{"one": 0 + 1, "two": 10 - 8, "three": 15 / 5}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + if len(hash.Pairs) != 3 { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } + + tests := map[string]func(ast.Expression){ + "one": func(e ast.Expression) { + testInfixExpression(t, e, 0, "+", 1) + }, + "two": func(e ast.Expression) { + testInfixExpression(t, e, 10, "-", 8) + }, + "three": func(e ast.Expression) { + testInfixExpression(t, e, 15, "/", 5) + }, + } + + for key, value := range hash.Pairs { + literal, ok := key.(*ast.StringLiteral) + if !ok { + t.Errorf("key is not ast.StringLiteral. got=%T", key) + continue + } + + testFunc, ok := tests[literal.String()] + if !ok { + t.Errorf("No test function for key %q found", literal.String()) + continue + } + + testFunc(value) + } +} + +func testLetStatement(t *testing.T, s ast.Statement, name string) bool { + if s.TokenLiteral() != "let" { + t.Errorf("s.TokenLiteral not 'let'. got=%q", s.TokenLiteral()) + return false + } + + letStmt, ok := s.(*ast.LetStatement) + if !ok { + t.Errorf("s not *ast.LetStatement. got=%T", s) + return false + } + + if letStmt.Name.Value != name { + t.Errorf("letStmt.Name.Value not '%s'. got=%s", name, letStmt.Name.Value) + return false + } + + if letStmt.Name.TokenLiteral() != name { + t.Errorf("letStmt.Name.TokenLiteral() not '%s'. got=%s", + name, letStmt.Name.TokenLiteral()) + return false + } + + return true +} + +func testInfixExpression(t *testing.T, exp ast.Expression, left interface{}, + operator string, right interface{}) bool { + + opExp, ok := exp.(*ast.InfixExpression) + if !ok { + t.Errorf("exp is not ast.InfixExpression. got=%T(%s)", exp, exp) + return false + } + + if !testLiteralExpression(t, opExp.Left, left) { + return false + } + + if opExp.Operator != operator { + t.Errorf("exp.Operator is not '%s'. got=%q", operator, opExp.Operator) + return false + } + + if !testLiteralExpression(t, opExp.Right, right) { + return false + } + + return true +} + +func testLiteralExpression( + t *testing.T, + exp ast.Expression, + expected interface{}, +) bool { + switch v := expected.(type) { + case int: + return testIntegerLiteral(t, exp, int64(v)) + case int64: + return testIntegerLiteral(t, exp, v) + case string: + return testIdentifier(t, exp, v) + case bool: + return testBooleanLiteral(t, exp, v) + } + t.Errorf("type of exp not handled. got=%T", exp) + return false +} + +func testIntegerLiteral(t *testing.T, il ast.Expression, value int64) bool { + integ, ok := il.(*ast.IntegerLiteral) + if !ok { + t.Errorf("il not *ast.IntegerLiteral. got=%T", il) + return false + } + + if integ.Value != value { + t.Errorf("integ.Value not %d. got=%d", value, integ.Value) + return false + } + + if integ.TokenLiteral() != fmt.Sprintf("%d", value) { + t.Errorf("integ.TokenLiteral not %d. got=%s", value, + integ.TokenLiteral()) + return false + } + + return true +} + +func testIdentifier(t *testing.T, exp ast.Expression, value string) bool { + ident, ok := exp.(*ast.Identifier) + if !ok { + t.Errorf("exp not *ast.Identifier. got=%T", exp) + return false + } + + if ident.Value != value { + t.Errorf("ident.Value not %s. got=%s", value, ident.Value) + return false + } + + if ident.TokenLiteral() != value { + t.Errorf("ident.TokenLiteral not %s. got=%s", value, + ident.TokenLiteral()) + return false + } + + return true +} + +func testBooleanLiteral(t *testing.T, exp ast.Expression, value bool) bool { + bo, ok := exp.(*ast.Boolean) + if !ok { + t.Errorf("exp not *ast.Boolean. got=%T", exp) + return false + } + + if bo.Value != value { + t.Errorf("bo.Value not %t. got=%t", value, bo.Value) + return false + } + + if bo.TokenLiteral() != fmt.Sprintf("%t", value) { + t.Errorf("bo.TokenLiteral not %t. got=%s", + value, bo.TokenLiteral()) + return false + } + + return true +} + +func checkParserErrors(t *testing.T, p *Parser) { + errors := p.Errors() + if len(errors) == 0 { + return + } + + t.Errorf("parser has %d errors", len(errors)) + for _, msg := range errors { + t.Errorf("parser error: %q", msg) + } + t.FailNow() +} diff --git a/wcig_code_1_2/06/src/monkey/parser/parser_tracing.go b/wcig_code_1_2/06/src/monkey/parser/parser_tracing.go new file mode 100644 index 0000000..5fc569b --- /dev/null +++ b/wcig_code_1_2/06/src/monkey/parser/parser_tracing.go @@ -0,0 +1,32 @@ +package parser + +import ( + "fmt" + "strings" +) + +var traceLevel int = 0 + +const traceIdentPlaceholder string = "\t" + +func identLevel() string { + return strings.Repeat(traceIdentPlaceholder, traceLevel-1) +} + +func tracePrint(fs string) { + fmt.Printf("%s%s\n", identLevel(), fs) +} + +func incIdent() { traceLevel = traceLevel + 1 } +func decIdent() { traceLevel = traceLevel - 1 } + +func trace(msg string) string { + incIdent() + tracePrint("BEGIN " + msg) + return msg +} + +func untrace(msg string) { + tracePrint("END " + msg) + decIdent() +} diff --git a/wcig_code_1_2/06/src/monkey/repl/repl.go b/wcig_code_1_2/06/src/monkey/repl/repl.go new file mode 100644 index 0000000..d14f36b --- /dev/null +++ b/wcig_code_1_2/06/src/monkey/repl/repl.go @@ -0,0 +1,83 @@ +package repl + +import ( + "bufio" + "fmt" + "io" + "monkey/compiler" + "monkey/lexer" + "monkey/object" + "monkey/parser" + "monkey/vm" +) + +const PROMPT = ">> " + +func Start(in io.Reader, out io.Writer) { + scanner := bufio.NewScanner(in) + + constants := []object.Object{} + globals := make([]object.Object, vm.GlobalsSize) + symbolTable := compiler.NewSymbolTable() + + for { + fmt.Fprintf(out, PROMPT) + scanned := scanner.Scan() + if !scanned { + return + } + + line := scanner.Text() + l := lexer.New(line) + p := parser.New(l) + + program := p.ParseProgram() + if len(p.Errors()) != 0 { + printParserErrors(out, p.Errors()) + continue + } + + comp := compiler.NewWithState(symbolTable, constants) + err := comp.Compile(program) + if err != nil { + fmt.Fprintf(out, "Woops! Compilation failed:\n %s\n", err) + continue + } + + code := comp.Bytecode() + constants = code.Constants + + machine := vm.NewWithGlobalsStore(code, globals) + err = machine.Run() + if err != nil { + fmt.Fprintf(out, "Woops! Executing bytecode failed:\n %s\n", err) + continue + } + + lastPopped := machine.LastPoppedStackElem() + io.WriteString(out, lastPopped.Inspect()) + io.WriteString(out, "\n") + } +} + +const MONKEY_FACE = ` __,__ + .--. .-" "-. .--. + / .. \/ .-. .-. \/ .. \ + | | '| / Y \ |' | | + | \ \ \ 0 | 0 / / / | + \ '- ,\.-"""""""-./, -' / + ''-' /_ ^ ^ _\ '-'' + | \._ _./ | + \ \ '~' / / + '._ '-=-' _.' + '-----' +` + +func printParserErrors(out io.Writer, errors []string) { + io.WriteString(out, MONKEY_FACE) + io.WriteString(out, "Woops! We ran into some monkey business here!\n") + io.WriteString(out, " parser errors:\n") + for _, msg := range errors { + io.WriteString(out, "\t"+msg+"\n") + } +} diff --git a/wcig_code_1_2/06/src/monkey/token/token.go b/wcig_code_1_2/06/src/monkey/token/token.go new file mode 100644 index 0000000..3d2d2f7 --- /dev/null +++ b/wcig_code_1_2/06/src/monkey/token/token.go @@ -0,0 +1,70 @@ +package token + +type TokenType string + +const ( + ILLEGAL = "ILLEGAL" + EOF = "EOF" + + // Identifiers + literals + IDENT = "IDENT" // add, foobar, x, y, ... + INT = "INT" // 1343456 + STRING = "STRING" // "foobar" + + // Operators + ASSIGN = "=" + PLUS = "+" + MINUS = "-" + BANG = "!" + ASTERISK = "*" + SLASH = "/" + + LT = "<" + GT = ">" + + EQ = "==" + NOT_EQ = "!=" + + // Delimiters + COMMA = "," + SEMICOLON = ";" + COLON = ":" + + LPAREN = "(" + RPAREN = ")" + LBRACE = "{" + RBRACE = "}" + LBRACKET = "[" + RBRACKET = "]" + + // Keywords + FUNCTION = "FUNCTION" + LET = "LET" + TRUE = "TRUE" + FALSE = "FALSE" + IF = "IF" + ELSE = "ELSE" + RETURN = "RETURN" +) + +type Token struct { + Type TokenType + Literal string +} + +var keywords = map[string]TokenType{ + "fn": FUNCTION, + "let": LET, + "true": TRUE, + "false": FALSE, + "if": IF, + "else": ELSE, + "return": RETURN, +} + +func LookupIdent(ident string) TokenType { + if tok, ok := keywords[ident]; ok { + return tok + } + return IDENT +} diff --git a/wcig_code_1_2/06/src/monkey/vm/vm.go b/wcig_code_1_2/06/src/monkey/vm/vm.go new file mode 100644 index 0000000..54e0c14 --- /dev/null +++ b/wcig_code_1_2/06/src/monkey/vm/vm.go @@ -0,0 +1,403 @@ +package vm + +import ( + "fmt" + "monkey/code" + "monkey/compiler" + "monkey/object" +) + +const StackSize = 2048 +const GlobalsSize = 65536 + +var True = &object.Boolean{Value: true} +var False = &object.Boolean{Value: false} +var Null = &object.Null{} + +type VM struct { + constants []object.Object + instructions code.Instructions + + stack []object.Object + sp int // Always points to the next value. Top of stack is stack[sp-1] + + globals []object.Object +} + +func New(bytecode *compiler.Bytecode) *VM { + return &VM{ + instructions: bytecode.Instructions, + constants: bytecode.Constants, + + stack: make([]object.Object, StackSize), + sp: 0, + + globals: make([]object.Object, GlobalsSize), + } +} + +func NewWithGlobalsStore(bytecode *compiler.Bytecode, s []object.Object) *VM { + vm := New(bytecode) + vm.globals = s + return vm +} + +func (vm *VM) LastPoppedStackElem() object.Object { + return vm.stack[vm.sp] +} + +func (vm *VM) Run() error { + for ip := 0; ip < len(vm.instructions); ip++ { + op := code.Opcode(vm.instructions[ip]) + + switch op { + case code.OpConstant: + constIndex := code.ReadUint16(vm.instructions[ip+1:]) + ip += 2 + + err := vm.push(vm.constants[constIndex]) + if err != nil { + return err + } + + case code.OpPop: + vm.pop() + + case code.OpAdd, code.OpSub, code.OpMul, code.OpDiv: + err := vm.executeBinaryOperation(op) + if err != nil { + return err + } + + case code.OpTrue: + err := vm.push(True) + if err != nil { + return err + } + + case code.OpFalse: + err := vm.push(False) + if err != nil { + return err + } + + case code.OpEqual, code.OpNotEqual, code.OpGreaterThan: + err := vm.executeComparison(op) + if err != nil { + return err + } + + case code.OpBang: + err := vm.executeBangOperator() + if err != nil { + return err + } + + case code.OpMinus: + err := vm.executeMinusOperator() + if err != nil { + return err + } + + case code.OpJump: + pos := int(code.ReadUint16(vm.instructions[ip+1:])) + ip = pos - 1 + + case code.OpJumpNotTruthy: + pos := int(code.ReadUint16(vm.instructions[ip+1:])) + ip += 2 + + condition := vm.pop() + if !isTruthy(condition) { + ip = pos - 1 + } + + case code.OpNull: + err := vm.push(Null) + if err != nil { + return err + } + + case code.OpSetGlobal: + globalIndex := code.ReadUint16(vm.instructions[ip+1:]) + ip += 2 + + vm.globals[globalIndex] = vm.pop() + + case code.OpGetGlobal: + globalIndex := code.ReadUint16(vm.instructions[ip+1:]) + ip += 2 + + err := vm.push(vm.globals[globalIndex]) + if err != nil { + return err + } + + case code.OpArray: + numElements := int(code.ReadUint16(vm.instructions[ip+1:])) + ip += 2 + + array := vm.buildArray(vm.sp-numElements, vm.sp) + vm.sp = vm.sp - numElements + + err := vm.push(array) + if err != nil { + return err + } + + case code.OpHash: + numElements := int(code.ReadUint16(vm.instructions[ip+1:])) + ip += 2 + + hash, err := vm.buildHash(vm.sp-numElements, vm.sp) + if err != nil { + return err + } + vm.sp = vm.sp - numElements + + err = vm.push(hash) + if err != nil { + return err + } + + case code.OpIndex: + index := vm.pop() + left := vm.pop() + + err := vm.executeIndexExpression(left, index) + if err != nil { + return err + } + } + } + + return nil +} + +func (vm *VM) push(o object.Object) error { + if vm.sp >= StackSize { + return fmt.Errorf("stack overflow") + } + + vm.stack[vm.sp] = o + vm.sp++ + + return nil +} + +func (vm *VM) pop() object.Object { + o := vm.stack[vm.sp-1] + vm.sp-- + return o +} + +func (vm *VM) executeBinaryOperation(op code.Opcode) error { + right := vm.pop() + left := vm.pop() + + leftType := left.Type() + rightType := right.Type() + + switch { + case leftType == object.INTEGER_OBJ && rightType == object.INTEGER_OBJ: + return vm.executeBinaryIntegerOperation(op, left, right) + case leftType == object.STRING_OBJ && rightType == object.STRING_OBJ: + return vm.executeBinaryStringOperation(op, left, right) + default: + return fmt.Errorf("unsupported types for binary operation: %s %s", + leftType, rightType) + } +} + +func (vm *VM) executeBinaryIntegerOperation( + op code.Opcode, + left, right object.Object, +) error { + leftValue := left.(*object.Integer).Value + rightValue := right.(*object.Integer).Value + + var result int64 + + switch op { + case code.OpAdd: + result = leftValue + rightValue + case code.OpSub: + result = leftValue - rightValue + case code.OpMul: + result = leftValue * rightValue + case code.OpDiv: + result = leftValue / rightValue + default: + return fmt.Errorf("unknown integer operator: %d", op) + } + + return vm.push(&object.Integer{Value: result}) +} + +func (vm *VM) executeComparison(op code.Opcode) error { + right := vm.pop() + left := vm.pop() + + if left.Type() == object.INTEGER_OBJ && right.Type() == object.INTEGER_OBJ { + return vm.executeIntegerComparison(op, left, right) + } + + switch op { + case code.OpEqual: + return vm.push(nativeBoolToBooleanObject(right == left)) + case code.OpNotEqual: + return vm.push(nativeBoolToBooleanObject(right != left)) + default: + return fmt.Errorf("unknown operator: %d (%s %s)", + op, left.Type(), right.Type()) + } +} + +func (vm *VM) executeIntegerComparison( + op code.Opcode, + left, right object.Object, +) error { + leftValue := left.(*object.Integer).Value + rightValue := right.(*object.Integer).Value + + switch op { + case code.OpEqual: + return vm.push(nativeBoolToBooleanObject(rightValue == leftValue)) + case code.OpNotEqual: + return vm.push(nativeBoolToBooleanObject(rightValue != leftValue)) + case code.OpGreaterThan: + return vm.push(nativeBoolToBooleanObject(leftValue > rightValue)) + default: + return fmt.Errorf("unknown operator: %d", op) + } +} + +func (vm *VM) executeBangOperator() error { + operand := vm.pop() + + switch operand { + case True: + return vm.push(False) + case False: + return vm.push(True) + case Null: + return vm.push(True) + default: + return vm.push(False) + } +} + +func (vm *VM) executeMinusOperator() error { + operand := vm.pop() + + if operand.Type() != object.INTEGER_OBJ { + return fmt.Errorf("unsupported type for negation: %s", operand.Type()) + } + + value := operand.(*object.Integer).Value + return vm.push(&object.Integer{Value: -value}) +} + +func (vm *VM) executeBinaryStringOperation( + op code.Opcode, + left, right object.Object, +) error { + if op != code.OpAdd { + return fmt.Errorf("unknown string operator: %d", op) + } + + leftValue := left.(*object.String).Value + rightValue := right.(*object.String).Value + + return vm.push(&object.String{Value: leftValue + rightValue}) +} + +func (vm *VM) buildArray(startIndex, endIndex int) object.Object { + elements := make([]object.Object, endIndex-startIndex) + + for i := startIndex; i < endIndex; i++ { + elements[i-startIndex] = vm.stack[i] + } + + return &object.Array{Elements: elements} +} + +func (vm *VM) buildHash(startIndex, endIndex int) (object.Object, error) { + hashedPairs := make(map[object.HashKey]object.HashPair) + + for i := startIndex; i < endIndex; i += 2 { + key := vm.stack[i] + value := vm.stack[i+1] + + pair := object.HashPair{Key: key, Value: value} + + hashKey, ok := key.(object.Hashable) + if !ok { + return nil, fmt.Errorf("unusable as hash key: %s", key.Type()) + } + + hashedPairs[hashKey.HashKey()] = pair + } + + return &object.Hash{Pairs: hashedPairs}, nil +} + +func (vm *VM) executeIndexExpression(left, index object.Object) error { + switch { + case left.Type() == object.ARRAY_OBJ && index.Type() == object.INTEGER_OBJ: + return vm.executeArrayIndex(left, index) + case left.Type() == object.HASH_OBJ: + return vm.executeHashIndex(left, index) + default: + return fmt.Errorf("index operator not supported: %s", left.Type()) + } +} + +func (vm *VM) executeArrayIndex(array, index object.Object) error { + arrayObject := array.(*object.Array) + i := index.(*object.Integer).Value + max := int64(len(arrayObject.Elements) - 1) + + if i < 0 || i > max { + return vm.push(Null) + } + + return vm.push(arrayObject.Elements[i]) +} + +func (vm *VM) executeHashIndex(hash, index object.Object) error { + hashObject := hash.(*object.Hash) + + key, ok := index.(object.Hashable) + if !ok { + return fmt.Errorf("unusable as hash key: %s", index.Type()) + } + + pair, ok := hashObject.Pairs[key.HashKey()] + if !ok { + return vm.push(Null) + } + + return vm.push(pair.Value) +} + +func nativeBoolToBooleanObject(input bool) *object.Boolean { + if input { + return True + } + return False +} + +func isTruthy(obj object.Object) bool { + switch obj := obj.(type) { + + case *object.Boolean: + return obj.Value + + case *object.Null: + return false + + default: + return true + } +} diff --git a/wcig_code_1_2/06/src/monkey/vm/vm_test.go b/wcig_code_1_2/06/src/monkey/vm/vm_test.go new file mode 100644 index 0000000..0e1f397 --- /dev/null +++ b/wcig_code_1_2/06/src/monkey/vm/vm_test.go @@ -0,0 +1,314 @@ +package vm + +import ( + "fmt" + "monkey/ast" + "monkey/compiler" + "monkey/lexer" + "monkey/object" + "monkey/parser" + "testing" +) + +func TestIntegerArithmetic(t *testing.T) { + tests := []vmTestCase{ + {"1", 1}, + {"2", 2}, + {"1 + 2", 3}, + {"1 - 2", -1}, + {"1 * 2", 2}, + {"4 / 2", 2}, + {"50 / 2 * 2 + 10 - 5", 55}, + {"5 * (2 + 10)", 60}, + {"5 + 5 + 5 + 5 - 10", 10}, + {"2 * 2 * 2 * 2 * 2", 32}, + {"5 * 2 + 10", 20}, + {"5 + 2 * 10", 25}, + {"5 * (2 + 10)", 60}, + {"-5", -5}, + {"-10", -10}, + {"-50 + 100 + -50", 0}, + {"(5 + 10 * 2 + 15 / 3) * 2 + -10", 50}, + } + + runVmTests(t, tests) +} + +func TestBooleanExpressions(t *testing.T) { + tests := []vmTestCase{ + {"true", true}, + {"false", false}, + {"1 < 2", true}, + {"1 > 2", false}, + {"1 < 1", false}, + {"1 > 1", false}, + {"1 == 1", true}, + {"1 != 1", false}, + {"1 == 2", false}, + {"1 != 2", true}, + {"true == true", true}, + {"false == false", true}, + {"true == false", false}, + {"true != false", true}, + {"false != true", true}, + {"(1 < 2) == true", true}, + {"(1 < 2) == false", false}, + {"(1 > 2) == true", false}, + {"(1 > 2) == false", true}, + {"!true", false}, + {"!false", true}, + {"!5", false}, + {"!!true", true}, + {"!!false", false}, + {"!!5", true}, + {"!(if (false) { 5; })", true}, + } + + runVmTests(t, tests) +} + +func TestConditionals(t *testing.T) { + tests := []vmTestCase{ + {"if (true) { 10 }", 10}, + {"if (true) { 10 } else { 20 }", 10}, + {"if (false) { 10 } else { 20 } ", 20}, + {"if (1) { 10 }", 10}, + {"if (1 < 2) { 10 }", 10}, + {"if (1 < 2) { 10 } else { 20 }", 10}, + {"if (1 > 2) { 10 } else { 20 }", 20}, + {"if (1 > 2) { 10 }", Null}, + {"if (false) { 10 }", Null}, + {"if ((if (false) { 10 })) { 10 } else { 20 }", 20}, + } + + runVmTests(t, tests) +} + +func TestGlobalLetStatements(t *testing.T) { + tests := []vmTestCase{ + {"let one = 1; one", 1}, + {"let one = 1; let two = 2; one + two", 3}, + {"let one = 1; let two = one + one; one + two", 3}, + } + + runVmTests(t, tests) +} + +func TestStringExpressions(t *testing.T) { + tests := []vmTestCase{ + {`"monkey"`, "monkey"}, + {`"mon" + "key"`, "monkey"}, + {`"mon" + "key" + "banana"`, "monkeybanana"}, + } + + runVmTests(t, tests) +} + +func TestArrayLiterals(t *testing.T) { + tests := []vmTestCase{ + {"[]", []int{}}, + {"[1, 2, 3]", []int{1, 2, 3}}, + {"[1 + 2, 3 * 4, 5 + 6]", []int{3, 12, 11}}, + } + + runVmTests(t, tests) +} + +func TestHashLiterals(t *testing.T) { + tests := []vmTestCase{ + { + "{}", map[object.HashKey]int64{}, + }, + { + "{1: 2, 2: 3}", + map[object.HashKey]int64{ + (&object.Integer{Value: 1}).HashKey(): 2, + (&object.Integer{Value: 2}).HashKey(): 3, + }, + }, + { + "{1 + 1: 2 * 2, 3 + 3: 4 * 4}", + map[object.HashKey]int64{ + (&object.Integer{Value: 2}).HashKey(): 4, + (&object.Integer{Value: 6}).HashKey(): 16, + }, + }, + } + + runVmTests(t, tests) +} + +func TestIndexExpressions(t *testing.T) { + tests := []vmTestCase{ + {"[1, 2, 3][1]", 2}, + {"[1, 2, 3][0 + 2]", 3}, + {"[[1, 1, 1]][0][0]", 1}, + {"[][0]", Null}, + {"[1, 2, 3][99]", Null}, + {"[1][-1]", Null}, + {"{1: 1, 2: 2}[1]", 1}, + {"{1: 1, 2: 2}[2]", 2}, + {"{1: 1}[0]", Null}, + {"{}[0]", Null}, + } + + runVmTests(t, tests) +} + +type vmTestCase struct { + input string + expected interface{} +} + +func runVmTests(t *testing.T, tests []vmTestCase) { + t.Helper() + + for _, tt := range tests { + program := parse(tt.input) + + comp := compiler.New() + err := comp.Compile(program) + if err != nil { + t.Fatalf("compiler error: %s", err) + } + + vm := New(comp.Bytecode()) + err = vm.Run() + if err != nil { + t.Fatalf("vm error: %s", err) + } + + stackElem := vm.LastPoppedStackElem() + + testExpectedObject(t, tt.expected, stackElem) + } +} + +func parse(input string) *ast.Program { + l := lexer.New(input) + p := parser.New(l) + return p.ParseProgram() +} + +func testExpectedObject( + t *testing.T, + expected interface{}, + actual object.Object, +) { + t.Helper() + + switch expected := expected.(type) { + case int: + err := testIntegerObject(int64(expected), actual) + if err != nil { + t.Errorf("testIntegerObject failed: %s", err) + } + + case bool: + err := testBooleanObject(bool(expected), actual) + if err != nil { + t.Errorf("testBooleanObject failed: %s", err) + } + + case *object.Null: + if actual != Null { + t.Errorf("object is not Null: %T (%+v)", actual, actual) + } + + case string: + err := testStringObject(expected, actual) + if err != nil { + t.Errorf("testStringObject failed: %s", err) + } + + case []int: + array, ok := actual.(*object.Array) + if !ok { + t.Errorf("object not Array: %T (%+v)", actual, actual) + return + } + + if len(array.Elements) != len(expected) { + t.Errorf("wrong num of elements. want=%d, got=%d", + len(expected), len(array.Elements)) + return + } + + for i, expectedElem := range expected { + err := testIntegerObject(int64(expectedElem), array.Elements[i]) + if err != nil { + t.Errorf("testIntegerObject failed: %s", err) + } + } + + case map[object.HashKey]int64: + hash, ok := actual.(*object.Hash) + if !ok { + t.Errorf("object is not Hash. got=%T (%+v)", actual, actual) + return + } + + if len(hash.Pairs) != len(expected) { + t.Errorf("hash has wrong number of Pairs. want=%d, got=%d", + len(expected), len(hash.Pairs)) + return + } + + for expectedKey, expectedValue := range expected { + pair, ok := hash.Pairs[expectedKey] + if !ok { + t.Errorf("no pair for given key in Pairs") + } + + err := testIntegerObject(expectedValue, pair.Value) + if err != nil { + t.Errorf("testIntegerObject failed: %s", err) + } + } + } +} + +func testIntegerObject(expected int64, actual object.Object) error { + result, ok := actual.(*object.Integer) + if !ok { + return fmt.Errorf("object is not Integer. got=%T (%+v)", + actual, actual) + } + + if result.Value != expected { + return fmt.Errorf("object has wrong value. got=%d, want=%d", + result.Value, expected) + } + + return nil +} + +func testBooleanObject(expected bool, actual object.Object) error { + result, ok := actual.(*object.Boolean) + if !ok { + return fmt.Errorf("object is not Boolean. got=%T (%+v)", + actual, actual) + } + + if result.Value != expected { + return fmt.Errorf("object has wrong value. got=%t, want=%t", + result.Value, expected) + } + + return nil +} + +func testStringObject(expected string, actual object.Object) error { + result, ok := actual.(*object.String) + if !ok { + return fmt.Errorf("object is not String. got=%T (%+v)", + actual, actual) + } + + if result.Value != expected { + return fmt.Errorf("object has wrong value. got=%q, want=%q", + result.Value, expected) + } + + return nil +} diff --git a/wcig_code_1_2/07/.envrc b/wcig_code_1_2/07/.envrc new file mode 100644 index 0000000..fb65ef4 --- /dev/null +++ b/wcig_code_1_2/07/.envrc @@ -0,0 +1 @@ +export GOPATH=$(pwd) diff --git a/wcig_code_1_2/07/src/monkey/ast/ast.go b/wcig_code_1_2/07/src/monkey/ast/ast.go new file mode 100644 index 0000000..fb30b05 --- /dev/null +++ b/wcig_code_1_2/07/src/monkey/ast/ast.go @@ -0,0 +1,339 @@ +package ast + +import ( + "bytes" + "monkey/token" + "strings" +) + +// The base Node interface +type Node interface { + TokenLiteral() string + String() string +} + +// All statement nodes implement this +type Statement interface { + Node + statementNode() +} + +// All expression nodes implement this +type Expression interface { + Node + expressionNode() +} + +type Program struct { + Statements []Statement +} + +func (p *Program) TokenLiteral() string { + if len(p.Statements) > 0 { + return p.Statements[0].TokenLiteral() + } else { + return "" + } +} + +func (p *Program) String() string { + var out bytes.Buffer + + for _, s := range p.Statements { + out.WriteString(s.String()) + } + + return out.String() +} + +// Statements +type LetStatement struct { + Token token.Token // the token.LET token + Name *Identifier + Value Expression +} + +func (ls *LetStatement) statementNode() {} +func (ls *LetStatement) TokenLiteral() string { return ls.Token.Literal } +func (ls *LetStatement) String() string { + var out bytes.Buffer + + out.WriteString(ls.TokenLiteral() + " ") + out.WriteString(ls.Name.String()) + out.WriteString(" = ") + + if ls.Value != nil { + out.WriteString(ls.Value.String()) + } + + out.WriteString(";") + + return out.String() +} + +type ReturnStatement struct { + Token token.Token // the 'return' token + ReturnValue Expression +} + +func (rs *ReturnStatement) statementNode() {} +func (rs *ReturnStatement) TokenLiteral() string { return rs.Token.Literal } +func (rs *ReturnStatement) String() string { + var out bytes.Buffer + + out.WriteString(rs.TokenLiteral() + " ") + + if rs.ReturnValue != nil { + out.WriteString(rs.ReturnValue.String()) + } + + out.WriteString(";") + + return out.String() +} + +type ExpressionStatement struct { + Token token.Token // the first token of the expression + Expression Expression +} + +func (es *ExpressionStatement) statementNode() {} +func (es *ExpressionStatement) TokenLiteral() string { return es.Token.Literal } +func (es *ExpressionStatement) String() string { + if es.Expression != nil { + return es.Expression.String() + } + return "" +} + +type BlockStatement struct { + Token token.Token // the { token + Statements []Statement +} + +func (bs *BlockStatement) statementNode() {} +func (bs *BlockStatement) TokenLiteral() string { return bs.Token.Literal } +func (bs *BlockStatement) String() string { + var out bytes.Buffer + + for _, s := range bs.Statements { + out.WriteString(s.String()) + } + + return out.String() +} + +// Expressions +type Identifier struct { + Token token.Token // the token.IDENT token + Value string +} + +func (i *Identifier) expressionNode() {} +func (i *Identifier) TokenLiteral() string { return i.Token.Literal } +func (i *Identifier) String() string { return i.Value } + +type Boolean struct { + Token token.Token + Value bool +} + +func (b *Boolean) expressionNode() {} +func (b *Boolean) TokenLiteral() string { return b.Token.Literal } +func (b *Boolean) String() string { return b.Token.Literal } + +type IntegerLiteral struct { + Token token.Token + Value int64 +} + +func (il *IntegerLiteral) expressionNode() {} +func (il *IntegerLiteral) TokenLiteral() string { return il.Token.Literal } +func (il *IntegerLiteral) String() string { return il.Token.Literal } + +type PrefixExpression struct { + Token token.Token // The prefix token, e.g. ! + Operator string + Right Expression +} + +func (pe *PrefixExpression) expressionNode() {} +func (pe *PrefixExpression) TokenLiteral() string { return pe.Token.Literal } +func (pe *PrefixExpression) String() string { + var out bytes.Buffer + + out.WriteString("(") + out.WriteString(pe.Operator) + out.WriteString(pe.Right.String()) + out.WriteString(")") + + return out.String() +} + +type InfixExpression struct { + Token token.Token // The operator token, e.g. + + Left Expression + Operator string + Right Expression +} + +func (oe *InfixExpression) expressionNode() {} +func (oe *InfixExpression) TokenLiteral() string { return oe.Token.Literal } +func (oe *InfixExpression) String() string { + var out bytes.Buffer + + out.WriteString("(") + out.WriteString(oe.Left.String()) + out.WriteString(" " + oe.Operator + " ") + out.WriteString(oe.Right.String()) + out.WriteString(")") + + return out.String() +} + +type IfExpression struct { + Token token.Token // The 'if' token + Condition Expression + Consequence *BlockStatement + Alternative *BlockStatement +} + +func (ie *IfExpression) expressionNode() {} +func (ie *IfExpression) TokenLiteral() string { return ie.Token.Literal } +func (ie *IfExpression) String() string { + var out bytes.Buffer + + out.WriteString("if") + out.WriteString(ie.Condition.String()) + out.WriteString(" ") + out.WriteString(ie.Consequence.String()) + + if ie.Alternative != nil { + out.WriteString("else ") + out.WriteString(ie.Alternative.String()) + } + + return out.String() +} + +type FunctionLiteral struct { + Token token.Token // The 'fn' token + Parameters []*Identifier + Body *BlockStatement +} + +func (fl *FunctionLiteral) expressionNode() {} +func (fl *FunctionLiteral) TokenLiteral() string { return fl.Token.Literal } +func (fl *FunctionLiteral) String() string { + var out bytes.Buffer + + params := []string{} + for _, p := range fl.Parameters { + params = append(params, p.String()) + } + + out.WriteString(fl.TokenLiteral()) + out.WriteString("(") + out.WriteString(strings.Join(params, ", ")) + out.WriteString(") ") + out.WriteString(fl.Body.String()) + + return out.String() +} + +type CallExpression struct { + Token token.Token // The '(' token + Function Expression // Identifier or FunctionLiteral + Arguments []Expression +} + +func (ce *CallExpression) expressionNode() {} +func (ce *CallExpression) TokenLiteral() string { return ce.Token.Literal } +func (ce *CallExpression) String() string { + var out bytes.Buffer + + args := []string{} + for _, a := range ce.Arguments { + args = append(args, a.String()) + } + + out.WriteString(ce.Function.String()) + out.WriteString("(") + out.WriteString(strings.Join(args, ", ")) + out.WriteString(")") + + return out.String() +} + +type StringLiteral struct { + Token token.Token + Value string +} + +func (sl *StringLiteral) expressionNode() {} +func (sl *StringLiteral) TokenLiteral() string { return sl.Token.Literal } +func (sl *StringLiteral) String() string { return sl.Token.Literal } + +type ArrayLiteral struct { + Token token.Token // the '[' token + Elements []Expression +} + +func (al *ArrayLiteral) expressionNode() {} +func (al *ArrayLiteral) TokenLiteral() string { return al.Token.Literal } +func (al *ArrayLiteral) String() string { + var out bytes.Buffer + + elements := []string{} + for _, el := range al.Elements { + elements = append(elements, el.String()) + } + + out.WriteString("[") + out.WriteString(strings.Join(elements, ", ")) + out.WriteString("]") + + return out.String() +} + +type IndexExpression struct { + Token token.Token // The [ token + Left Expression + Index Expression +} + +func (ie *IndexExpression) expressionNode() {} +func (ie *IndexExpression) TokenLiteral() string { return ie.Token.Literal } +func (ie *IndexExpression) String() string { + var out bytes.Buffer + + out.WriteString("(") + out.WriteString(ie.Left.String()) + out.WriteString("[") + out.WriteString(ie.Index.String()) + out.WriteString("])") + + return out.String() +} + +type HashLiteral struct { + Token token.Token // the '{' token + Pairs map[Expression]Expression +} + +func (hl *HashLiteral) expressionNode() {} +func (hl *HashLiteral) TokenLiteral() string { return hl.Token.Literal } +func (hl *HashLiteral) String() string { + var out bytes.Buffer + + pairs := []string{} + for key, value := range hl.Pairs { + pairs = append(pairs, key.String()+":"+value.String()) + } + + out.WriteString("{") + out.WriteString(strings.Join(pairs, ", ")) + out.WriteString("}") + + return out.String() +} diff --git a/wcig_code_1_2/07/src/monkey/ast/ast_test.go b/wcig_code_1_2/07/src/monkey/ast/ast_test.go new file mode 100644 index 0000000..14a49dc --- /dev/null +++ b/wcig_code_1_2/07/src/monkey/ast/ast_test.go @@ -0,0 +1,28 @@ +package ast + +import ( + "monkey/token" + "testing" +) + +func TestString(t *testing.T) { + program := &Program{ + Statements: []Statement{ + &LetStatement{ + Token: token.Token{Type: token.LET, Literal: "let"}, + Name: &Identifier{ + Token: token.Token{Type: token.IDENT, Literal: "myVar"}, + Value: "myVar", + }, + Value: &Identifier{ + Token: token.Token{Type: token.IDENT, Literal: "anotherVar"}, + Value: "anotherVar", + }, + }, + }, + } + + if program.String() != "let myVar = anotherVar;" { + t.Errorf("program.String() wrong. got=%q", program.String()) + } +} diff --git a/wcig_code_1_2/07/src/monkey/code/code.go b/wcig_code_1_2/07/src/monkey/code/code.go new file mode 100644 index 0000000..f3852d8 --- /dev/null +++ b/wcig_code_1_2/07/src/monkey/code/code.go @@ -0,0 +1,201 @@ +package code + +import ( + "bytes" + "encoding/binary" + "fmt" +) + +type Instructions []byte + +func (ins Instructions) String() string { + var out bytes.Buffer + + i := 0 + for i < len(ins) { + def, err := Lookup(ins[i]) + if err != nil { + fmt.Fprintf(&out, "ERROR: %s\n", err) + continue + } + + operands, read := ReadOperands(def, ins[i+1:]) + + fmt.Fprintf(&out, "%04d %s\n", i, ins.fmtInstruction(def, operands)) + + i += 1 + read + } + + return out.String() +} + +func (ins Instructions) fmtInstruction(def *Definition, operands []int) string { + operandCount := len(def.OperandWidths) + + if len(operands) != operandCount { + return fmt.Sprintf("ERROR: operand len %d does not match defined %d\n", + len(operands), operandCount) + } + + switch operandCount { + case 0: + return def.Name + case 1: + return fmt.Sprintf("%s %d", def.Name, operands[0]) + } + + return fmt.Sprintf("ERROR: unhandled operandCount for %s\n", def.Name) +} + +type Opcode byte + +const ( + OpConstant Opcode = iota + + OpAdd + + OpPop + + OpSub + OpMul + OpDiv + + OpTrue + OpFalse + + OpEqual + OpNotEqual + OpGreaterThan + + OpMinus + OpBang + + OpJumpNotTruthy + OpJump + + OpNull + + OpGetGlobal + OpSetGlobal + + OpArray + OpHash + OpIndex + + OpCall + + OpReturnValue + OpReturn + + OpGetLocal + OpSetLocal +) + +type Definition struct { + Name string + OperandWidths []int +} + +var definitions = map[Opcode]*Definition{ + OpConstant: {"OpConstant", []int{2}}, + + OpAdd: {"OpAdd", []int{}}, + + OpPop: {"OpPop", []int{}}, + + OpSub: {"OpSub", []int{}}, + OpMul: {"OpMul", []int{}}, + OpDiv: {"OpDiv", []int{}}, + + OpTrue: {"OpTrue", []int{}}, + OpFalse: {"OpFalse", []int{}}, + + OpEqual: {"OpEqual", []int{}}, + OpNotEqual: {"OpNotEqual", []int{}}, + OpGreaterThan: {"OpGreaterThan", []int{}}, + + OpMinus: {"OpMinus", []int{}}, + OpBang: {"OpBang", []int{}}, + + OpJumpNotTruthy: {"OpJumpNotTruthy", []int{2}}, + OpJump: {"OpJump", []int{2}}, + + OpNull: {"OpNull", []int{}}, + + OpGetGlobal: {"OpGetGlobal", []int{2}}, + OpSetGlobal: {"OpSetGlobal", []int{2}}, + + OpArray: {"OpArray", []int{2}}, + OpHash: {"OpHash", []int{2}}, + OpIndex: {"OpIndex", []int{}}, + + OpCall: {"OpCall", []int{1}}, + + OpReturnValue: {"OpReturnValue", []int{}}, + OpReturn: {"OpReturn", []int{}}, + + OpGetLocal: {"OpGetLocal", []int{1}}, + OpSetLocal: {"OpSetLocal", []int{1}}, +} + +func Lookup(op byte) (*Definition, error) { + def, ok := definitions[Opcode(op)] + if !ok { + return nil, fmt.Errorf("opcode %d undefined", op) + } + + return def, nil +} + +func Make(op Opcode, operands ...int) []byte { + def, ok := definitions[op] + if !ok { + return []byte{} + } + + instructionLen := 1 + for _, w := range def.OperandWidths { + instructionLen += w + } + + instruction := make([]byte, instructionLen) + instruction[0] = byte(op) + + offset := 1 + for i, o := range operands { + width := def.OperandWidths[i] + switch width { + case 2: + binary.BigEndian.PutUint16(instruction[offset:], uint16(o)) + case 1: + instruction[offset] = byte(o) + } + offset += width + } + + return instruction +} + +func ReadOperands(def *Definition, ins Instructions) ([]int, int) { + operands := make([]int, len(def.OperandWidths)) + offset := 0 + + for i, width := range def.OperandWidths { + switch width { + case 2: + operands[i] = int(ReadUint16(ins[offset:])) + case 1: + operands[i] = int(ReadUint8(ins[offset:])) + } + + offset += width + } + + return operands, offset +} + +func ReadUint8(ins Instructions) uint8 { return uint8(ins[0]) } + +func ReadUint16(ins Instructions) uint16 { + return binary.BigEndian.Uint16(ins) +} diff --git a/wcig_code_1_2/07/src/monkey/code/code_test.go b/wcig_code_1_2/07/src/monkey/code/code_test.go new file mode 100644 index 0000000..71f8f73 --- /dev/null +++ b/wcig_code_1_2/07/src/monkey/code/code_test.go @@ -0,0 +1,87 @@ +package code + +import "testing" + +func TestMake(t *testing.T) { + tests := []struct { + op Opcode + operands []int + expected []byte + }{ + {OpConstant, []int{65534}, []byte{byte(OpConstant), 255, 254}}, + {OpAdd, []int{}, []byte{byte(OpAdd)}}, + {OpGetLocal, []int{255}, []byte{byte(OpGetLocal), 255}}, + } + + for _, tt := range tests { + instruction := Make(tt.op, tt.operands...) + + if len(instruction) != len(tt.expected) { + t.Errorf("instruction has wrong length. want=%d, got=%d", + len(tt.expected), len(instruction)) + } + + for i, b := range tt.expected { + if instruction[i] != tt.expected[i] { + t.Errorf("wrong byte at pos %d. want=%d, got=%d", + i, b, instruction[i]) + } + } + } +} + +func TestInstructionsString(t *testing.T) { + instructions := []Instructions{ + Make(OpAdd), + Make(OpGetLocal, 1), + Make(OpConstant, 2), + Make(OpConstant, 65535), + } + + expected := `0000 OpAdd +0001 OpGetLocal 1 +0003 OpConstant 2 +0006 OpConstant 65535 +` + + concatted := Instructions{} + for _, ins := range instructions { + concatted = append(concatted, ins...) + } + + if concatted.String() != expected { + t.Errorf("instructions wrongly formatted.\nwant=%q\ngot=%q", + expected, concatted.String()) + } +} + +func TestReadOperands(t *testing.T) { + tests := []struct { + op Opcode + operands []int + bytesRead int + }{ + {OpConstant, []int{65535}, 2}, + {OpGetLocal, []int{255}, 1}, + } + + for _, tt := range tests { + instruction := Make(tt.op, tt.operands...) + + def, err := Lookup(byte(tt.op)) + if err != nil { + t.Fatalf("definition not found: %q\n", err) + } + + operandsRead, n := ReadOperands(def, instruction[1:]) + if n != tt.bytesRead { + t.Fatalf("n wrong. want=%d, got=%d", tt.bytesRead, n) + } + + for i, want := range tt.operands { + if operandsRead[i] != want { + t.Errorf("operand wrong. want=%d, got=%d", want, operandsRead[i]) + } + } + } +} diff --git a/wcig_code_1_2/07/src/monkey/compiler/compiler.go b/wcig_code_1_2/07/src/monkey/compiler/compiler.go new file mode 100644 index 0000000..6525a51 --- /dev/null +++ b/wcig_code_1_2/07/src/monkey/compiler/compiler.go @@ -0,0 +1,428 @@ +package compiler + +import ( + "fmt" + "monkey/ast" + "monkey/code" + "monkey/object" + "sort" +) + +type Compiler struct { + constants []object.Object + + symbolTable *SymbolTable + + scopes []CompilationScope + scopeIndex int +} + +func New() *Compiler { + mainScope := CompilationScope{ + instructions: code.Instructions{}, + lastInstruction: EmittedInstruction{}, + previousInstruction: EmittedInstruction{}, + } + + return &Compiler{ + constants: []object.Object{}, + symbolTable: NewSymbolTable(), + scopes: []CompilationScope{mainScope}, + scopeIndex: 0, + } +} + +func NewWithState(s *SymbolTable, constants []object.Object) *Compiler { + compiler := New() + compiler.symbolTable = s + compiler.constants = constants + return compiler +} + +func (c *Compiler) Compile(node ast.Node) error { + switch node := node.(type) { + case *ast.Program: + for _, s := range node.Statements { + err := c.Compile(s) + if err != nil { + return err + } + } + + case *ast.ExpressionStatement: + err := c.Compile(node.Expression) + if err != nil { + return err + } + c.emit(code.OpPop) + + case *ast.InfixExpression: + if node.Operator == "<" { + err := c.Compile(node.Right) + if err != nil { + return err + } + + err = c.Compile(node.Left) + if err != nil { + return err + } + c.emit(code.OpGreaterThan) + return nil + } + + err := c.Compile(node.Left) + if err != nil { + return err + } + + err = c.Compile(node.Right) + if err != nil { + return err + } + + switch node.Operator { + case "+": + c.emit(code.OpAdd) + case "-": + c.emit(code.OpSub) + case "*": + c.emit(code.OpMul) + case "/": + c.emit(code.OpDiv) + case ">": + c.emit(code.OpGreaterThan) + case "==": + c.emit(code.OpEqual) + case "!=": + c.emit(code.OpNotEqual) + default: + return fmt.Errorf("unknown operator %s", node.Operator) + } + + case *ast.IntegerLiteral: + integer := &object.Integer{Value: node.Value} + c.emit(code.OpConstant, c.addConstant(integer)) + + case *ast.Boolean: + if node.Value { + c.emit(code.OpTrue) + } else { + c.emit(code.OpFalse) + } + + case *ast.PrefixExpression: + err := c.Compile(node.Right) + if err != nil { + return err + } + + switch node.Operator { + case "!": + c.emit(code.OpBang) + case "-": + c.emit(code.OpMinus) + default: + return fmt.Errorf("unknown operator %s", node.Operator) + } + + case *ast.IfExpression: + err := c.Compile(node.Condition) + if err != nil { + return err + } + + // Emit an `OpJumpNotTruthy` with a bogus value + jumpNotTruthyPos := c.emit(code.OpJumpNotTruthy, 9999) + + err = c.Compile(node.Consequence) + if err != nil { + return err + } + + if c.lastInstructionIs(code.OpPop) { + c.removeLastPop() + } + + // Emit an `OpJump` with a bogus value + jumpPos := c.emit(code.OpJump, 9999) + + afterConsequencePos := len(c.currentInstructions()) + c.changeOperand(jumpNotTruthyPos, afterConsequencePos) + + if node.Alternative == nil { + c.emit(code.OpNull) + } else { + err := c.Compile(node.Alternative) + if err != nil { + return err + } + + if c.lastInstructionIs(code.OpPop) { + c.removeLastPop() + } + } + + afterAlternativePos := len(c.currentInstructions()) + c.changeOperand(jumpPos, afterAlternativePos) + + case *ast.BlockStatement: + for _, s := range node.Statements { + err := c.Compile(s) + if err != nil { + return err + } + } + + case *ast.LetStatement: + err := c.Compile(node.Value) + if err != nil { + return err + } + + symbol := c.symbolTable.Define(node.Name.Value) + if symbol.Scope == GlobalScope { + c.emit(code.OpSetGlobal, symbol.Index) + } else { + c.emit(code.OpSetLocal, symbol.Index) + } + + case *ast.Identifier: + symbol, ok := c.symbolTable.Resolve(node.Value) + if !ok { + return fmt.Errorf("undefined variable %s", node.Value) + } + + if symbol.Scope == GlobalScope { + c.emit(code.OpGetGlobal, symbol.Index) + } else { + c.emit(code.OpGetLocal, symbol.Index) + } + + case *ast.StringLiteral: + str := &object.String{Value: node.Value} + c.emit(code.OpConstant, c.addConstant(str)) + + case *ast.ArrayLiteral: + for _, el := range node.Elements { + err := c.Compile(el) + if err != nil { + return err + } + } + + c.emit(code.OpArray, len(node.Elements)) + + case *ast.HashLiteral: + keys := []ast.Expression{} + for k := range node.Pairs { + keys = append(keys, k) + } + sort.Slice(keys, func(i, j int) bool { + return keys[i].String() < keys[j].String() + }) + + for _, k := range keys { + err := c.Compile(k) + if err != nil { + return err + } + err = c.Compile(node.Pairs[k]) + if err != nil { + return err + } + } + + c.emit(code.OpHash, len(node.Pairs)*2) + + case *ast.IndexExpression: + err := c.Compile(node.Left) + if err != nil { + return err + } + + err = c.Compile(node.Index) + if err != nil { + return err + } + + c.emit(code.OpIndex) + + case *ast.FunctionLiteral: + c.enterScope() + + for _, p := range node.Parameters { + c.symbolTable.Define(p.Value) + } + + err := c.Compile(node.Body) + if err != nil { + return err + } + + if c.lastInstructionIs(code.OpPop) { + c.replaceLastPopWithReturn() + } + if !c.lastInstructionIs(code.OpReturnValue) { + c.emit(code.OpReturn) + } + + numLocals := c.symbolTable.numDefinitions + instructions := c.leaveScope() + + compiledFn := &object.CompiledFunction{ + Instructions: instructions, + NumLocals: numLocals, + NumParameters: len(node.Parameters), + } + c.emit(code.OpConstant, c.addConstant(compiledFn)) + + case *ast.ReturnStatement: + err := c.Compile(node.ReturnValue) + if err != nil { + return err + } + + c.emit(code.OpReturnValue) + + case *ast.CallExpression: + err := c.Compile(node.Function) + if err != nil { + return err + } + + for _, a := range node.Arguments { + err := c.Compile(a) + if err != nil { + return err + } + } + + c.emit(code.OpCall, len(node.Arguments)) + + } + + return nil +} + +func (c *Compiler) Bytecode() *Bytecode { + return &Bytecode{ + Instructions: c.currentInstructions(), + Constants: c.constants, + } +} + +func (c *Compiler) addConstant(obj object.Object) int { + c.constants = append(c.constants, obj) + return len(c.constants) - 1 +} + +func (c *Compiler) emit(op code.Opcode, operands ...int) int { + ins := code.Make(op, operands...) + pos := c.addInstruction(ins) + + c.setLastInstruction(op, pos) + + return pos +} + +func (c *Compiler) addInstruction(ins []byte) int { + posNewInstruction := len(c.currentInstructions()) + updatedInstructions := append(c.currentInstructions(), ins...) + + c.scopes[c.scopeIndex].instructions = updatedInstructions + + return posNewInstruction +} + +func (c *Compiler) setLastInstruction(op code.Opcode, pos int) { + previous := c.scopes[c.scopeIndex].lastInstruction + last := EmittedInstruction{Opcode: op, Position: pos} + + c.scopes[c.scopeIndex].previousInstruction = previous + c.scopes[c.scopeIndex].lastInstruction = last +} + +func (c *Compiler) lastInstructionIs(op code.Opcode) bool { + if len(c.currentInstructions()) == 0 { + return false + } + + return c.scopes[c.scopeIndex].lastInstruction.Opcode == op +} + +func (c *Compiler) removeLastPop() { + last := c.scopes[c.scopeIndex].lastInstruction + previous := c.scopes[c.scopeIndex].previousInstruction + + old := c.currentInstructions() + new := old[:last.Position] + + c.scopes[c.scopeIndex].instructions = new + c.scopes[c.scopeIndex].lastInstruction = previous +} + +func (c *Compiler) replaceInstruction(pos int, newInstruction []byte) { + ins := c.currentInstructions() + + for i := 0; i < len(newInstruction); i++ { + ins[pos+i] = newInstruction[i] + } +} + +func (c *Compiler) changeOperand(opPos int, operand int) { + op := code.Opcode(c.currentInstructions()[opPos]) + newInstruction := code.Make(op, operand) + + c.replaceInstruction(opPos, newInstruction) +} + +func (c *Compiler) currentInstructions() code.Instructions { + return c.scopes[c.scopeIndex].instructions +} + +func (c *Compiler) enterScope() { + scope := CompilationScope{ + instructions: code.Instructions{}, + lastInstruction: EmittedInstruction{}, + previousInstruction: EmittedInstruction{}, + } + c.scopes = append(c.scopes, scope) + c.scopeIndex++ + + c.symbolTable = NewEnclosedSymbolTable(c.symbolTable) +} + +func (c *Compiler) leaveScope() code.Instructions { + instructions := c.currentInstructions() + + c.scopes = c.scopes[:len(c.scopes)-1] + c.scopeIndex-- + + c.symbolTable = c.symbolTable.Outer + + return instructions +} + +func (c *Compiler) replaceLastPopWithReturn() { + lastPos := c.scopes[c.scopeIndex].lastInstruction.Position + c.replaceInstruction(lastPos, code.Make(code.OpReturnValue)) + + c.scopes[c.scopeIndex].lastInstruction.Opcode = code.OpReturnValue +} + +type Bytecode struct { + Instructions code.Instructions + Constants []object.Object +} + +type EmittedInstruction struct { + Opcode code.Opcode + Position int +} + +type CompilationScope struct { + instructions code.Instructions + lastInstruction EmittedInstruction + previousInstruction EmittedInstruction +} diff --git a/wcig_code_1_2/07/src/monkey/compiler/compiler_test.go b/wcig_code_1_2/07/src/monkey/compiler/compiler_test.go new file mode 100644 index 0000000..11c840d --- /dev/null +++ b/wcig_code_1_2/07/src/monkey/compiler/compiler_test.go @@ -0,0 +1,870 @@ +package compiler + +import ( + "fmt" + "monkey/ast" + "monkey/code" + "monkey/lexer" + "monkey/object" + "monkey/parser" + "testing" +) + +func TestCompilerScopes(t *testing.T) { + compiler := New() + if compiler.scopeIndex != 0 { + t.Errorf("scopeIndex wrong. got=%d, want=%d", compiler.scopeIndex, 0) + } + globalSymbolTable := compiler.symbolTable + + compiler.emit(code.OpMul) + + compiler.enterScope() + if compiler.scopeIndex != 1 { + t.Errorf("scopeIndex wrong. got=%d, want=%d", compiler.scopeIndex, 1) + } + + compiler.emit(code.OpSub) + + if len(compiler.scopes[compiler.scopeIndex].instructions) != 1 { + t.Errorf("instructions length wrong. got=%d", + len(compiler.scopes[compiler.scopeIndex].instructions)) + } + + last := compiler.scopes[compiler.scopeIndex].lastInstruction + if last.Opcode != code.OpSub { + t.Errorf("lastInstruction.Opcode wrong. got=%d, want=%d", + last.Opcode, code.OpSub) + } + + if compiler.symbolTable.Outer != globalSymbolTable { + t.Errorf("compiler did not enclose symbolTable") + } + + compiler.leaveScope() + if compiler.scopeIndex != 0 { + t.Errorf("scopeIndex wrong. got=%d, want=%d", + compiler.scopeIndex, 0) + } + + if compiler.symbolTable != globalSymbolTable { + t.Errorf("compiler did not restore global symbol table") + } + if compiler.symbolTable.Outer != nil { + t.Errorf("compiler modified global symbol table incorrectly") + } + + compiler.emit(code.OpAdd) + + if len(compiler.scopes[compiler.scopeIndex].instructions) != 2 { + t.Errorf("instructions length wrong. got=%d", + len(compiler.scopes[compiler.scopeIndex].instructions)) + } + + last = compiler.scopes[compiler.scopeIndex].lastInstruction + if last.Opcode != code.OpAdd { + t.Errorf("lastInstruction.Opcode wrong. got=%d, want=%d", + last.Opcode, code.OpAdd) + } + + previous := compiler.scopes[compiler.scopeIndex].previousInstruction + if previous.Opcode != code.OpMul { + t.Errorf("previousInstruction.Opcode wrong. got=%d, want=%d", + previous.Opcode, code.OpMul) + } +} + +func TestIntegerArithmetic(t *testing.T) { + tests := []compilerTestCase{ + { + input: "1 + 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpAdd), + code.Make(code.OpPop), + }, + }, + { + input: "1; 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpPop), + code.Make(code.OpConstant, 1), + code.Make(code.OpPop), + }, + }, + { + input: "1 - 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpSub), + code.Make(code.OpPop), + }, + }, + { + input: "1 * 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpMul), + code.Make(code.OpPop), + }, + }, + { + input: "2 / 1", + expectedConstants: []interface{}{2, 1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpDiv), + code.Make(code.OpPop), + }, + }, + { + input: "-1", + expectedConstants: []interface{}{1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpMinus), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestBooleanExpressions(t *testing.T) { + tests := []compilerTestCase{ + { + input: "true", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpTrue), + code.Make(code.OpPop), + }, + }, + { + input: "false", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpFalse), + code.Make(code.OpPop), + }, + }, + { + input: "1 > 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpGreaterThan), + code.Make(code.OpPop), + }, + }, + { + input: "1 < 2", + expectedConstants: []interface{}{2, 1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpGreaterThan), + code.Make(code.OpPop), + }, + }, + { + input: "1 == 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpEqual), + code.Make(code.OpPop), + }, + }, + { + input: "1 != 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpNotEqual), + code.Make(code.OpPop), + }, + }, + { + input: "true == false", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpTrue), + code.Make(code.OpFalse), + code.Make(code.OpEqual), + code.Make(code.OpPop), + }, + }, + { + input: "true != false", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpTrue), + code.Make(code.OpFalse), + code.Make(code.OpNotEqual), + code.Make(code.OpPop), + }, + }, + { + input: "!true", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpTrue), + code.Make(code.OpBang), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestConditionals(t *testing.T) { + tests := []compilerTestCase{ + { + input: ` + if (true) { 10 }; 3333; + `, + expectedConstants: []interface{}{10, 3333}, + expectedInstructions: []code.Instructions{ + // 0000 + code.Make(code.OpTrue), + // 0001 + code.Make(code.OpJumpNotTruthy, 10), + // 0004 + code.Make(code.OpConstant, 0), + // 0007 + code.Make(code.OpJump, 11), + // 0010 + code.Make(code.OpNull), + // 0011 + code.Make(code.OpPop), + // 0012 + code.Make(code.OpConstant, 1), + // 0015 + code.Make(code.OpPop), + }, + }, + { + input: ` + if (true) { 10 } else { 20 }; 3333; + `, + expectedConstants: []interface{}{10, 20, 3333}, + expectedInstructions: []code.Instructions{ + // 0000 + code.Make(code.OpTrue), + // 0001 + code.Make(code.OpJumpNotTruthy, 10), + // 0004 + code.Make(code.OpConstant, 0), + // 0007 + code.Make(code.OpJump, 13), + // 0010 + code.Make(code.OpConstant, 1), + // 0013 + code.Make(code.OpPop), + // 0014 + code.Make(code.OpConstant, 2), + // 0017 + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestGlobalLetStatements(t *testing.T) { + tests := []compilerTestCase{ + { + input: ` + let one = 1; + let two = 2; + `, + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpSetGlobal, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpSetGlobal, 1), + }, + }, + { + input: ` + let one = 1; + one; + `, + expectedConstants: []interface{}{1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpSetGlobal, 0), + code.Make(code.OpGetGlobal, 0), + code.Make(code.OpPop), + }, + }, + { + input: ` + let one = 1; + let two = one; + two; + `, + expectedConstants: []interface{}{1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpSetGlobal, 0), + code.Make(code.OpGetGlobal, 0), + code.Make(code.OpSetGlobal, 1), + code.Make(code.OpGetGlobal, 1), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestFunctions(t *testing.T) { + tests := []compilerTestCase{ + { + input: `fn() { return 5 + 10 }`, + expectedConstants: []interface{}{ + 5, + 10, + []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpAdd), + code.Make(code.OpReturnValue), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 2), + code.Make(code.OpPop), + }, + }, + { + input: `fn() { 5 + 10 }`, + expectedConstants: []interface{}{ + 5, + 10, + []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpAdd), + code.Make(code.OpReturnValue), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 2), + code.Make(code.OpPop), + }, + }, + { + input: `fn() { 1; 2 }`, + expectedConstants: []interface{}{ + 1, + 2, + []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpPop), + code.Make(code.OpConstant, 1), + code.Make(code.OpReturnValue), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 2), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +type compilerTestCase struct { + input string + expectedConstants []interface{} + expectedInstructions []code.Instructions +} + +func runCompilerTests(t *testing.T, tests []compilerTestCase) { + t.Helper() + + for _, tt := range tests { + program := parse(tt.input) + + compiler := New() + err := compiler.Compile(program) + if err != nil { + t.Fatalf("compiler error: %s", err) + } + + bytecode := compiler.Bytecode() + + err = testInstructions(tt.expectedInstructions, bytecode.Instructions) + if err != nil { + t.Fatalf("testInstructions failed: %s", err) + } + + err = testConstants(t, tt.expectedConstants, bytecode.Constants) + if err != nil { + t.Fatalf("testConstants failed: %s", err) + } + } +} + +func TestStringExpressions(t *testing.T) { + tests := []compilerTestCase{ + { + input: `"monkey"`, + expectedConstants: []interface{}{"monkey"}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpPop), + }, + }, + { + input: `"mon" + "key"`, + expectedConstants: []interface{}{"mon", "key"}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpAdd), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestArrayLiterals(t *testing.T) { + tests := []compilerTestCase{ + { + input: "[]", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpArray, 0), + code.Make(code.OpPop), + }, + }, + { + input: "[1, 2, 3]", + expectedConstants: []interface{}{1, 2, 3}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpConstant, 2), + code.Make(code.OpArray, 3), + code.Make(code.OpPop), + }, + }, + { + input: "[1 + 2, 3 - 4, 5 * 6]", + expectedConstants: []interface{}{1, 2, 3, 4, 5, 6}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpAdd), + code.Make(code.OpConstant, 2), + code.Make(code.OpConstant, 3), + code.Make(code.OpSub), + code.Make(code.OpConstant, 4), + code.Make(code.OpConstant, 5), + code.Make(code.OpMul), + code.Make(code.OpArray, 3), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestHashLiterals(t *testing.T) { + tests := []compilerTestCase{ + { + input: "{}", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpHash, 0), + code.Make(code.OpPop), + }, + }, + { + input: "{1: 2, 3: 4, 5: 6}", + expectedConstants: []interface{}{1, 2, 3, 4, 5, 6}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpConstant, 2), + code.Make(code.OpConstant, 3), + code.Make(code.OpConstant, 4), + code.Make(code.OpConstant, 5), + code.Make(code.OpHash, 6), + code.Make(code.OpPop), + }, + }, + { + input: "{1: 2 + 3, 4: 5 * 6}", + expectedConstants: []interface{}{1, 2, 3, 4, 5, 6}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpConstant, 2), + code.Make(code.OpAdd), + code.Make(code.OpConstant, 3), + code.Make(code.OpConstant, 4), + code.Make(code.OpConstant, 5), + code.Make(code.OpMul), + code.Make(code.OpHash, 4), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestIndexExpressions(t *testing.T) { + tests := []compilerTestCase{ + { + input: "[1, 2, 3][1 + 1]", + expectedConstants: []interface{}{1, 2, 3, 1, 1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpConstant, 2), + code.Make(code.OpArray, 3), + code.Make(code.OpConstant, 3), + code.Make(code.OpConstant, 4), + code.Make(code.OpAdd), + code.Make(code.OpIndex), + code.Make(code.OpPop), + }, + }, + { + input: "{1: 2}[2 - 1]", + expectedConstants: []interface{}{1, 2, 2, 1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpHash, 2), + code.Make(code.OpConstant, 2), + code.Make(code.OpConstant, 3), + code.Make(code.OpSub), + code.Make(code.OpIndex), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestFunctionsWithoutReturnValue(t *testing.T) { + tests := []compilerTestCase{ + { + input: `fn() { }`, + expectedConstants: []interface{}{ + []code.Instructions{ + code.Make(code.OpReturn), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestFunctionCalls(t *testing.T) { + tests := []compilerTestCase{ + { + input: `fn() { 24 }();`, + expectedConstants: []interface{}{ + 24, + []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpReturnValue), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 1), + code.Make(code.OpCall, 0), + code.Make(code.OpPop), + }, + }, + { + input: ` + let noArg = fn() { 24 }; + noArg(); + `, + expectedConstants: []interface{}{ + 24, + []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpReturnValue), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 1), + code.Make(code.OpSetGlobal, 0), + code.Make(code.OpGetGlobal, 0), + code.Make(code.OpCall, 0), + code.Make(code.OpPop), + }, + }, + { + input: ` + let oneArg = fn(a) { a }; + oneArg(24); + `, + expectedConstants: []interface{}{ + []code.Instructions{ + code.Make(code.OpGetLocal, 0), + code.Make(code.OpReturnValue), + }, + 24, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpSetGlobal, 0), + code.Make(code.OpGetGlobal, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpCall, 1), + code.Make(code.OpPop), + }, + }, + { + input: ` + let manyArg = fn(a, b, c) { a; b; c }; + manyArg(24, 25, 26); + `, + expectedConstants: []interface{}{ + []code.Instructions{ + code.Make(code.OpGetLocal, 0), + code.Make(code.OpPop), + code.Make(code.OpGetLocal, 1), + code.Make(code.OpPop), + code.Make(code.OpGetLocal, 2), + code.Make(code.OpReturnValue), + }, + 24, + 25, + 26, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpSetGlobal, 0), + code.Make(code.OpGetGlobal, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpConstant, 2), + code.Make(code.OpConstant, 3), + code.Make(code.OpCall, 3), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestLetStatementScopes(t *testing.T) { + tests := []compilerTestCase{ + { + input: ` + let num = 55; + fn() { num } + `, + expectedConstants: []interface{}{ + 55, + []code.Instructions{ + code.Make(code.OpGetGlobal, 0), + code.Make(code.OpReturnValue), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpSetGlobal, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpPop), + }, + }, + { + input: ` + fn() { + let num = 55; + num + } + `, + expectedConstants: []interface{}{ + 55, + []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpSetLocal, 0), + code.Make(code.OpGetLocal, 0), + code.Make(code.OpReturnValue), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 1), + code.Make(code.OpPop), + }, + }, + { + input: ` + fn() { + let a = 55; + let b = 77; + a + b + } + `, + expectedConstants: []interface{}{ + 55, + 77, + []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpSetLocal, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpSetLocal, 1), + code.Make(code.OpGetLocal, 0), + code.Make(code.OpGetLocal, 1), + code.Make(code.OpAdd), + code.Make(code.OpReturnValue), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 2), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func parse(input string) *ast.Program { + l := lexer.New(input) + p := parser.New(l) + return p.ParseProgram() +} + +func testInstructions( + expected []code.Instructions, + actual code.Instructions, +) error { + concatted := concatInstructions(expected) + + if len(actual) != len(concatted) { + return fmt.Errorf("wrong instructions length.\nwant=%q\ngot =%q", + concatted, actual) + } + + for i, ins := range concatted { + if actual[i] != ins { + return fmt.Errorf("wrong instruction at %d.\nwant=%q\ngot =%q", + i, concatted, actual) + } + } + + return nil +} + +func concatInstructions(s []code.Instructions) code.Instructions { + out := code.Instructions{} + + for _, ins := range s { + out = append(out, ins...) + } + + return out +} + +func testConstants( + t *testing.T, + expected []interface{}, + actual []object.Object, +) error { + if len(expected) != len(actual) { + return fmt.Errorf("wrong number of constants. got=%d, want=%d", + len(actual), len(expected)) + } + + for i, constant := range expected { + switch constant := constant.(type) { + case string: + err := testStringObject(constant, actual[i]) + if err != nil { + return fmt.Errorf("constant %d - testStringObject failed: %s", + i, err) + } + case int: + err := testIntegerObject(int64(constant), actual[i]) + if err != nil { + return fmt.Errorf("constant %d - testIntegerObject failed: %s", + i, err) + } + case []code.Instructions: + fn, ok := actual[i].(*object.CompiledFunction) + if !ok { + return fmt.Errorf("constant %d - not a function: %T", + i, actual[i]) + } + + err := testInstructions(constant, fn.Instructions) + if err != nil { + return fmt.Errorf("constant %d - testInstructions failed: %s", + i, err) + } + } + } + + return nil +} + +func testIntegerObject(expected int64, actual object.Object) error { + result, ok := actual.(*object.Integer) + if !ok { + return fmt.Errorf("object is not Integer. got=%T (%+v)", + actual, actual) + } + + if result.Value != expected { + return fmt.Errorf("object has wrong value. got=%d, want=%d", + result.Value, expected) + } + + return nil +} + +func testStringObject(expected string, actual object.Object) error { + result, ok := actual.(*object.String) + if !ok { + return fmt.Errorf("object is not String. got=%T (%+v)", + actual, actual) + } + + if result.Value != expected { + return fmt.Errorf("object has wrong value. got=%q, want=%q", + result.Value, expected) + } + + return nil +} diff --git a/wcig_code_1_2/07/src/monkey/compiler/symbol_table.go b/wcig_code_1_2/07/src/monkey/compiler/symbol_table.go new file mode 100644 index 0000000..ea518b9 --- /dev/null +++ b/wcig_code_1_2/07/src/monkey/compiler/symbol_table.go @@ -0,0 +1,54 @@ +package compiler + +type SymbolScope string + +const ( + LocalScope SymbolScope = "LOCAL" + GlobalScope SymbolScope = "GLOBAL" +) + +type Symbol struct { + Name string + Scope SymbolScope + Index int +} + +type SymbolTable struct { + Outer *SymbolTable + + store map[string]Symbol + numDefinitions int +} + +func NewEnclosedSymbolTable(outer *SymbolTable) *SymbolTable { + s := NewSymbolTable() + s.Outer = outer + return s +} + +func NewSymbolTable() *SymbolTable { + s := make(map[string]Symbol) + return &SymbolTable{store: s} +} + +func (s *SymbolTable) Define(name string) Symbol { + symbol := Symbol{Name: name, Index: s.numDefinitions} + if s.Outer == nil { + symbol.Scope = GlobalScope + } else { + symbol.Scope = LocalScope + } + + s.store[name] = symbol + s.numDefinitions++ + return symbol +} + +func (s *SymbolTable) Resolve(name string) (Symbol, bool) { + obj, ok := s.store[name] + if !ok && s.Outer != nil { + obj, ok = s.Outer.Resolve(name) + return obj, ok + } + return obj, ok +} diff --git a/wcig_code_1_2/07/src/monkey/compiler/symbol_table_test.go b/wcig_code_1_2/07/src/monkey/compiler/symbol_table_test.go new file mode 100644 index 0000000..4aa6d10 --- /dev/null +++ b/wcig_code_1_2/07/src/monkey/compiler/symbol_table_test.go @@ -0,0 +1,154 @@ +package compiler + +import "testing" + +func TestDefine(t *testing.T) { + expected := map[string]Symbol{ + "a": Symbol{Name: "a", Scope: GlobalScope, Index: 0}, + "b": Symbol{Name: "b", Scope: GlobalScope, Index: 1}, + "c": Symbol{Name: "c", Scope: LocalScope, Index: 0}, + "d": Symbol{Name: "d", Scope: LocalScope, Index: 1}, + "e": Symbol{Name: "e", Scope: LocalScope, Index: 0}, + "f": Symbol{Name: "f", Scope: LocalScope, Index: 1}, + } + + global := NewSymbolTable() + + a := global.Define("a") + if a != expected["a"] { + t.Errorf("expected a=%+v, got=%+v", expected["a"], a) + } + + b := global.Define("b") + if b != expected["b"] { + t.Errorf("expected b=%+v, got=%+v", expected["b"], b) + } + + firstLocal := NewEnclosedSymbolTable(global) + + c := firstLocal.Define("c") + if c != expected["c"] { + t.Errorf("expected c=%+v, got=%+v", expected["c"], c) + } + + d := firstLocal.Define("d") + if d != expected["d"] { + t.Errorf("expected d=%+v, got=%+v", expected["d"], d) + } + + secondLocal := NewEnclosedSymbolTable(firstLocal) + + e := secondLocal.Define("e") + if e != expected["e"] { + t.Errorf("expected e=%+v, got=%+v", expected["e"], e) + } + + f := secondLocal.Define("f") + if f != expected["f"] { + t.Errorf("expected f=%+v, got=%+v", expected["f"], f) + } +} + +func TestResolveGlobal(t *testing.T) { + global := NewSymbolTable() + global.Define("a") + global.Define("b") + + expected := []Symbol{ + Symbol{Name: "a", Scope: GlobalScope, Index: 0}, + Symbol{Name: "b", Scope: GlobalScope, Index: 1}, + } + + for _, sym := range expected { + result, ok := global.Resolve(sym.Name) + if !ok { + t.Errorf("name %s not resolvable", sym.Name) + continue + } + if result != sym { + t.Errorf("expected %s to resolve to %+v, got=%+v", + sym.Name, sym, result) + } + } +} + +func TestResolveLocal(t *testing.T) { + global := NewSymbolTable() + global.Define("a") + global.Define("b") + + local := NewEnclosedSymbolTable(global) + local.Define("c") + local.Define("d") + + expected := []Symbol{ + Symbol{Name: "a", Scope: GlobalScope, Index: 0}, + Symbol{Name: "b", Scope: GlobalScope, Index: 1}, + Symbol{Name: "c", Scope: LocalScope, Index: 0}, + Symbol{Name: "d", Scope: LocalScope, Index: 1}, + } + + for _, sym := range expected { + result, ok := local.Resolve(sym.Name) + if !ok { + t.Errorf("name %s not resolvable", sym.Name) + continue + } + if result != sym { + t.Errorf("expected %s to resolve to %+v, got=%+v", + sym.Name, sym, result) + } + } +} + +func TestResolveNestedLocal(t *testing.T) { + global := NewSymbolTable() + global.Define("a") + global.Define("b") + + firstLocal := NewEnclosedSymbolTable(global) + firstLocal.Define("c") + firstLocal.Define("d") + + secondLocal := NewEnclosedSymbolTable(firstLocal) + secondLocal.Define("e") + secondLocal.Define("f") + + tests := []struct { + table *SymbolTable + expectedSymbols []Symbol + }{ + { + firstLocal, + []Symbol{ + Symbol{Name: "a", Scope: GlobalScope, Index: 0}, + Symbol{Name: "b", Scope: GlobalScope, Index: 1}, + Symbol{Name: "c", Scope: LocalScope, Index: 0}, + Symbol{Name: "d", Scope: LocalScope, Index: 1}, + }, + }, + { + secondLocal, + []Symbol{ + Symbol{Name: "a", Scope: GlobalScope, Index: 0}, + Symbol{Name: "b", Scope: GlobalScope, Index: 1}, + Symbol{Name: "e", Scope: LocalScope, Index: 0}, + Symbol{Name: "f", Scope: LocalScope, Index: 1}, + }, + }, + } + + for _, tt := range tests { + for _, sym := range tt.expectedSymbols { + result, ok := tt.table.Resolve(sym.Name) + if !ok { + t.Errorf("name %s not resolvable", sym.Name) + continue + } + if result != sym { + t.Errorf("expected %s to resolve to %+v, got=%+v", + sym.Name, sym, result) + } + } + } +} diff --git a/wcig_code_1_2/07/src/monkey/evaluator/builtins.go b/wcig_code_1_2/07/src/monkey/evaluator/builtins.go new file mode 100644 index 0000000..c4fb0f3 --- /dev/null +++ b/wcig_code_1_2/07/src/monkey/evaluator/builtins.go @@ -0,0 +1,117 @@ +package evaluator + +import ( + "fmt" + "monkey/object" +) + +var builtins = map[string]*object.Builtin{ + "len": &object.Builtin{Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", + len(args)) + } + + switch arg := args[0].(type) { + case *object.Array: + return &object.Integer{Value: int64(len(arg.Elements))} + case *object.String: + return &object.Integer{Value: int64(len(arg.Value))} + default: + return newError("argument to `len` not supported, got %s", + args[0].Type()) + } + }, + }, + "puts": &object.Builtin{ + Fn: func(args ...object.Object) object.Object { + for _, arg := range args { + fmt.Println(arg.Inspect()) + } + + return NULL + }, + }, + "first": &object.Builtin{ + Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", + len(args)) + } + if args[0].Type() != object.ARRAY_OBJ { + return newError("argument to `first` must be ARRAY, got %s", + args[0].Type()) + } + + arr := args[0].(*object.Array) + if len(arr.Elements) > 0 { + return arr.Elements[0] + } + + return NULL + }, + }, + "last": &object.Builtin{ + Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", + len(args)) + } + if args[0].Type() != object.ARRAY_OBJ { + return newError("argument to `last` must be ARRAY, got %s", + args[0].Type()) + } + + arr := args[0].(*object.Array) + length := len(arr.Elements) + if length > 0 { + return arr.Elements[length-1] + } + + return NULL + }, + }, + "rest": &object.Builtin{ + Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", + len(args)) + } + if args[0].Type() != object.ARRAY_OBJ { + return newError("argument to `rest` must be ARRAY, got %s", + args[0].Type()) + } + + arr := args[0].(*object.Array) + length := len(arr.Elements) + if length > 0 { + newElements := make([]object.Object, length-1, length-1) + copy(newElements, arr.Elements[1:length]) + return &object.Array{Elements: newElements} + } + + return NULL + }, + }, + "push": &object.Builtin{ + Fn: func(args ...object.Object) object.Object { + if len(args) != 2 { + return newError("wrong number of arguments. got=%d, want=2", + len(args)) + } + if args[0].Type() != object.ARRAY_OBJ { + return newError("argument to `push` must be ARRAY, got %s", + args[0].Type()) + } + + arr := args[0].(*object.Array) + length := len(arr.Elements) + + newElements := make([]object.Object, length+1, length+1) + copy(newElements, arr.Elements) + newElements[length] = args[1] + + return &object.Array{Elements: newElements} + }, + }, +} diff --git a/wcig_code_1_2/07/src/monkey/evaluator/evaluator.go b/wcig_code_1_2/07/src/monkey/evaluator/evaluator.go new file mode 100644 index 0000000..50b2ab5 --- /dev/null +++ b/wcig_code_1_2/07/src/monkey/evaluator/evaluator.go @@ -0,0 +1,442 @@ +package evaluator + +import ( + "fmt" + "monkey/ast" + "monkey/object" +) + +var ( + NULL = &object.Null{} + TRUE = &object.Boolean{Value: true} + FALSE = &object.Boolean{Value: false} +) + +func Eval(node ast.Node, env *object.Environment) object.Object { + switch node := node.(type) { + + // Statements + case *ast.Program: + return evalProgram(node, env) + + case *ast.BlockStatement: + return evalBlockStatement(node, env) + + case *ast.ExpressionStatement: + return Eval(node.Expression, env) + + case *ast.ReturnStatement: + val := Eval(node.ReturnValue, env) + if isError(val) { + return val + } + return &object.ReturnValue{Value: val} + + case *ast.LetStatement: + val := Eval(node.Value, env) + if isError(val) { + return val + } + env.Set(node.Name.Value, val) + + // Expressions + case *ast.IntegerLiteral: + return &object.Integer{Value: node.Value} + + case *ast.StringLiteral: + return &object.String{Value: node.Value} + + case *ast.Boolean: + return nativeBoolToBooleanObject(node.Value) + + case *ast.PrefixExpression: + right := Eval(node.Right, env) + if isError(right) { + return right + } + return evalPrefixExpression(node.Operator, right) + + case *ast.InfixExpression: + left := Eval(node.Left, env) + if isError(left) { + return left + } + + right := Eval(node.Right, env) + if isError(right) { + return right + } + + return evalInfixExpression(node.Operator, left, right) + + case *ast.IfExpression: + return evalIfExpression(node, env) + + case *ast.Identifier: + return evalIdentifier(node, env) + + case *ast.FunctionLiteral: + params := node.Parameters + body := node.Body + return &object.Function{Parameters: params, Env: env, Body: body} + + case *ast.CallExpression: + function := Eval(node.Function, env) + if isError(function) { + return function + } + + args := evalExpressions(node.Arguments, env) + if len(args) == 1 && isError(args[0]) { + return args[0] + } + + return applyFunction(function, args) + + case *ast.ArrayLiteral: + elements := evalExpressions(node.Elements, env) + if len(elements) == 1 && isError(elements[0]) { + return elements[0] + } + return &object.Array{Elements: elements} + + case *ast.IndexExpression: + left := Eval(node.Left, env) + if isError(left) { + return left + } + index := Eval(node.Index, env) + if isError(index) { + return index + } + return evalIndexExpression(left, index) + + case *ast.HashLiteral: + return evalHashLiteral(node, env) + + } + + return nil +} + +func evalProgram(program *ast.Program, env *object.Environment) object.Object { + var result object.Object + + for _, statement := range program.Statements { + result = Eval(statement, env) + + switch result := result.(type) { + case *object.ReturnValue: + return result.Value + case *object.Error: + return result + } + } + + return result +} + +func evalBlockStatement( + block *ast.BlockStatement, + env *object.Environment, +) object.Object { + var result object.Object + + for _, statement := range block.Statements { + result = Eval(statement, env) + + if result != nil { + rt := result.Type() + if rt == object.RETURN_VALUE_OBJ || rt == object.ERROR_OBJ { + return result + } + } + } + + return result +} + +func nativeBoolToBooleanObject(input bool) *object.Boolean { + if input { + return TRUE + } + return FALSE +} + +func evalPrefixExpression(operator string, right object.Object) object.Object { + switch operator { + case "!": + return evalBangOperatorExpression(right) + case "-": + return evalMinusPrefixOperatorExpression(right) + default: + return newError("unknown operator: %s%s", operator, right.Type()) + } +} + +func evalInfixExpression( + operator string, + left, right object.Object, +) object.Object { + switch { + case left.Type() == object.INTEGER_OBJ && right.Type() == object.INTEGER_OBJ: + return evalIntegerInfixExpression(operator, left, right) + case left.Type() == object.STRING_OBJ && right.Type() == object.STRING_OBJ: + return evalStringInfixExpression(operator, left, right) + case operator == "==": + return nativeBoolToBooleanObject(left == right) + case operator == "!=": + return nativeBoolToBooleanObject(left != right) + case left.Type() != right.Type(): + return newError("type mismatch: %s %s %s", + left.Type(), operator, right.Type()) + default: + return newError("unknown operator: %s %s %s", + left.Type(), operator, right.Type()) + } +} + +func evalBangOperatorExpression(right object.Object) object.Object { + switch right { + case TRUE: + return FALSE + case FALSE: + return TRUE + case NULL: + return TRUE + default: + return FALSE + } +} + +func evalMinusPrefixOperatorExpression(right object.Object) object.Object { + if right.Type() != object.INTEGER_OBJ { + return newError("unknown operator: -%s", right.Type()) + } + + value := right.(*object.Integer).Value + return &object.Integer{Value: -value} +} + +func evalIntegerInfixExpression( + operator string, + left, right object.Object, +) object.Object { + leftVal := left.(*object.Integer).Value + rightVal := right.(*object.Integer).Value + + switch operator { + case "+": + return &object.Integer{Value: leftVal + rightVal} + case "-": + return &object.Integer{Value: leftVal - rightVal} + case "*": + return &object.Integer{Value: leftVal * rightVal} + case "/": + return &object.Integer{Value: leftVal / rightVal} + case "<": + return nativeBoolToBooleanObject(leftVal < rightVal) + case ">": + return nativeBoolToBooleanObject(leftVal > rightVal) + case "==": + return nativeBoolToBooleanObject(leftVal == rightVal) + case "!=": + return nativeBoolToBooleanObject(leftVal != rightVal) + default: + return newError("unknown operator: %s %s %s", + left.Type(), operator, right.Type()) + } +} + +func evalStringInfixExpression( + operator string, + left, right object.Object, +) object.Object { + if operator != "+" { + return newError("unknown operator: %s %s %s", + left.Type(), operator, right.Type()) + } + + leftVal := left.(*object.String).Value + rightVal := right.(*object.String).Value + return &object.String{Value: leftVal + rightVal} +} + +func evalIfExpression( + ie *ast.IfExpression, + env *object.Environment, +) object.Object { + condition := Eval(ie.Condition, env) + if isError(condition) { + return condition + } + + if isTruthy(condition) { + return Eval(ie.Consequence, env) + } else if ie.Alternative != nil { + return Eval(ie.Alternative, env) + } else { + return NULL + } +} + +func evalIdentifier( + node *ast.Identifier, + env *object.Environment, +) object.Object { + if val, ok := env.Get(node.Value); ok { + return val + } + + if builtin, ok := builtins[node.Value]; ok { + return builtin + } + + return newError("identifier not found: " + node.Value) +} + +func isTruthy(obj object.Object) bool { + switch obj { + case NULL: + return false + case TRUE: + return true + case FALSE: + return false + default: + return true + } +} + +func newError(format string, a ...interface{}) *object.Error { + return &object.Error{Message: fmt.Sprintf(format, a...)} +} + +func isError(obj object.Object) bool { + if obj != nil { + return obj.Type() == object.ERROR_OBJ + } + return false +} + +func evalExpressions( + exps []ast.Expression, + env *object.Environment, +) []object.Object { + var result []object.Object + + for _, e := range exps { + evaluated := Eval(e, env) + if isError(evaluated) { + return []object.Object{evaluated} + } + result = append(result, evaluated) + } + + return result +} + +func applyFunction(fn object.Object, args []object.Object) object.Object { + switch fn := fn.(type) { + + case *object.Function: + extendedEnv := extendFunctionEnv(fn, args) + evaluated := Eval(fn.Body, extendedEnv) + return unwrapReturnValue(evaluated) + + case *object.Builtin: + return fn.Fn(args...) + + default: + return newError("not a function: %s", fn.Type()) + } +} + +func extendFunctionEnv( + fn *object.Function, + args []object.Object, +) *object.Environment { + env := object.NewEnclosedEnvironment(fn.Env) + + for paramIdx, param := range fn.Parameters { + env.Set(param.Value, args[paramIdx]) + } + + return env +} + +func unwrapReturnValue(obj object.Object) object.Object { + if returnValue, ok := obj.(*object.ReturnValue); ok { + return returnValue.Value + } + + return obj +} + +func evalIndexExpression(left, index object.Object) object.Object { + switch { + case left.Type() == object.ARRAY_OBJ && index.Type() == object.INTEGER_OBJ: + return evalArrayIndexExpression(left, index) + case left.Type() == object.HASH_OBJ: + return evalHashIndexExpression(left, index) + default: + return newError("index operator not supported: %s", left.Type()) + } +} + +func evalArrayIndexExpression(array, index object.Object) object.Object { + arrayObject := array.(*object.Array) + idx := index.(*object.Integer).Value + max := int64(len(arrayObject.Elements) - 1) + + if idx < 0 || idx > max { + return NULL + } + + return arrayObject.Elements[idx] +} + +func evalHashLiteral( + node *ast.HashLiteral, + env *object.Environment, +) object.Object { + pairs := make(map[object.HashKey]object.HashPair) + + for keyNode, valueNode := range node.Pairs { + key := Eval(keyNode, env) + if isError(key) { + return key + } + + hashKey, ok := key.(object.Hashable) + if !ok { + return newError("unusable as hash key: %s", key.Type()) + } + + value := Eval(valueNode, env) + if isError(value) { + return value + } + + hashed := hashKey.HashKey() + pairs[hashed] = object.HashPair{Key: key, Value: value} + } + + return &object.Hash{Pairs: pairs} +} + +func evalHashIndexExpression(hash, index object.Object) object.Object { + hashObject := hash.(*object.Hash) + + key, ok := index.(object.Hashable) + if !ok { + return newError("unusable as hash key: %s", index.Type()) + } + + pair, ok := hashObject.Pairs[key.HashKey()] + if !ok { + return NULL + } + + return pair.Value +} diff --git a/wcig_code_1_2/07/src/monkey/evaluator/evaluator_test.go b/wcig_code_1_2/07/src/monkey/evaluator/evaluator_test.go new file mode 100644 index 0000000..2304e6f --- /dev/null +++ b/wcig_code_1_2/07/src/monkey/evaluator/evaluator_test.go @@ -0,0 +1,629 @@ +package evaluator + +import ( + "monkey/lexer" + "monkey/object" + "monkey/parser" + "testing" +) + +func TestEvalIntegerExpression(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"5", 5}, + {"10", 10}, + {"-5", -5}, + {"-10", -10}, + {"5 + 5 + 5 + 5 - 10", 10}, + {"2 * 2 * 2 * 2 * 2", 32}, + {"-50 + 100 + -50", 0}, + {"5 * 2 + 10", 20}, + {"5 + 2 * 10", 25}, + {"20 + 2 * -10", 0}, + {"50 / 2 * 2 + 10", 60}, + {"2 * (5 + 10)", 30}, + {"3 * 3 * 3 + 10", 37}, + {"3 * (3 * 3) + 10", 37}, + {"(5 + 10 * 2 + 15 / 3) * 2 + -10", 50}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testIntegerObject(t, evaluated, tt.expected) + } +} + +func TestEvalBooleanExpression(t *testing.T) { + tests := []struct { + input string + expected bool + }{ + {"true", true}, + {"false", false}, + {"1 < 2", true}, + {"1 > 2", false}, + {"1 < 1", false}, + {"1 > 1", false}, + {"1 == 1", true}, + {"1 != 1", false}, + {"1 == 2", false}, + {"1 != 2", true}, + {"true == true", true}, + {"false == false", true}, + {"true == false", false}, + {"true != false", true}, + {"false != true", true}, + {"(1 < 2) == true", true}, + {"(1 < 2) == false", false}, + {"(1 > 2) == true", false}, + {"(1 > 2) == false", true}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testBooleanObject(t, evaluated, tt.expected) + } +} + +func TestBangOperator(t *testing.T) { + tests := []struct { + input string + expected bool + }{ + {"!true", false}, + {"!false", true}, + {"!5", false}, + {"!!true", true}, + {"!!false", false}, + {"!!5", true}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testBooleanObject(t, evaluated, tt.expected) + } +} + +func TestIfElseExpressions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + {"if (true) { 10 }", 10}, + {"if (false) { 10 }", nil}, + {"if (1) { 10 }", 10}, + {"if (1 < 2) { 10 }", 10}, + {"if (1 > 2) { 10 }", nil}, + {"if (1 > 2) { 10 } else { 20 }", 20}, + {"if (1 < 2) { 10 } else { 20 }", 10}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + integer, ok := tt.expected.(int) + if ok { + testIntegerObject(t, evaluated, int64(integer)) + } else { + testNullObject(t, evaluated) + } + } +} + +func TestReturnStatements(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"return 10;", 10}, + {"return 10; 9;", 10}, + {"return 2 * 5; 9;", 10}, + {"9; return 2 * 5; 9;", 10}, + {"if (10 > 1) { return 10; }", 10}, + { + ` +if (10 > 1) { + if (10 > 1) { + return 10; + } + + return 1; +} +`, + 10, + }, + { + ` +let f = fn(x) { + return x; + x + 10; +}; +f(10);`, + 10, + }, + { + ` +let f = fn(x) { + let result = x + 10; + return result; + return 10; +}; +f(10);`, + 20, + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testIntegerObject(t, evaluated, tt.expected) + } +} + +func TestErrorHandling(t *testing.T) { + tests := []struct { + input string + expectedMessage string + }{ + { + "5 + true;", + "type mismatch: INTEGER + BOOLEAN", + }, + { + "5 + true; 5;", + "type mismatch: INTEGER + BOOLEAN", + }, + { + "-true", + "unknown operator: -BOOLEAN", + }, + { + "true + false;", + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + "true + false + true + false;", + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + "5; true + false; 5", + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + `"Hello" - "World"`, + "unknown operator: STRING - STRING", + }, + { + "if (10 > 1) { true + false; }", + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + ` +if (10 > 1) { + if (10 > 1) { + return true + false; + } + + return 1; +} +`, + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + "foobar", + "identifier not found: foobar", + }, + { + `{"name": "Monkey"}[fn(x) { x }];`, + "unusable as hash key: FUNCTION", + }, + { + `999[1]`, + "index operator not supported: INTEGER", + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + + errObj, ok := evaluated.(*object.Error) + if !ok { + t.Errorf("no error object returned. got=%T(%+v)", + evaluated, evaluated) + continue + } + + if errObj.Message != tt.expectedMessage { + t.Errorf("wrong error message. expected=%q, got=%q", + tt.expectedMessage, errObj.Message) + } + } +} + +func TestLetStatements(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"let a = 5; a;", 5}, + {"let a = 5 * 5; a;", 25}, + {"let a = 5; let b = a; b;", 5}, + {"let a = 5; let b = a; let c = a + b + 5; c;", 15}, + } + + for _, tt := range tests { + testIntegerObject(t, testEval(tt.input), tt.expected) + } +} + +func TestFunctionObject(t *testing.T) { + input := "fn(x) { x + 2; };" + + evaluated := testEval(input) + fn, ok := evaluated.(*object.Function) + if !ok { + t.Fatalf("object is not Function. got=%T (%+v)", evaluated, evaluated) + } + + if len(fn.Parameters) != 1 { + t.Fatalf("function has wrong parameters. Parameters=%+v", + fn.Parameters) + } + + if fn.Parameters[0].String() != "x" { + t.Fatalf("parameter is not 'x'. got=%q", fn.Parameters[0]) + } + + expectedBody := "(x + 2)" + + if fn.Body.String() != expectedBody { + t.Fatalf("body is not %q. got=%q", expectedBody, fn.Body.String()) + } +} + +func TestFunctionApplication(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"let identity = fn(x) { x; }; identity(5);", 5}, + {"let identity = fn(x) { return x; }; identity(5);", 5}, + {"let double = fn(x) { x * 2; }; double(5);", 10}, + {"let add = fn(x, y) { x + y; }; add(5, 5);", 10}, + {"let add = fn(x, y) { x + y; }; add(5 + 5, add(5, 5));", 20}, + {"fn(x) { x; }(5)", 5}, + } + + for _, tt := range tests { + testIntegerObject(t, testEval(tt.input), tt.expected) + } +} + +func TestEnclosingEnvironments(t *testing.T) { + input := ` +let first = 10; +let second = 10; +let third = 10; + +let ourFunction = fn(first) { + let second = 20; + + first + second + third; +}; + +ourFunction(20) + first + second;` + + testIntegerObject(t, testEval(input), 70) +} + +func TestClosures(t *testing.T) { + input := ` +let newAdder = fn(x) { + fn(y) { x + y }; +}; + +let addTwo = newAdder(2); +addTwo(2);` + + testIntegerObject(t, testEval(input), 4) +} + +func TestStringLiteral(t *testing.T) { + input := `"Hello World!"` + + evaluated := testEval(input) + str, ok := evaluated.(*object.String) + if !ok { + t.Fatalf("object is not String. got=%T (%+v)", evaluated, evaluated) + } + + if str.Value != "Hello World!" { + t.Errorf("String has wrong value. got=%q", str.Value) + } +} + +func TestStringConcatenation(t *testing.T) { + input := `"Hello" + " " + "World!"` + + evaluated := testEval(input) + str, ok := evaluated.(*object.String) + if !ok { + t.Fatalf("object is not String. got=%T (%+v)", evaluated, evaluated) + } + + if str.Value != "Hello World!" { + t.Errorf("String has wrong value. got=%q", str.Value) + } +} + +func TestBuiltinFunctions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + {`len("")`, 0}, + {`len("four")`, 4}, + {`len("hello world")`, 11}, + {`len(1)`, "argument to `len` not supported, got INTEGER"}, + {`len("one", "two")`, "wrong number of arguments. got=2, want=1"}, + {`len([1, 2, 3])`, 3}, + {`len([])`, 0}, + {`puts("hello", "world!")`, nil}, + {`first([1, 2, 3])`, 1}, + {`first([])`, nil}, + {`first(1)`, "argument to `first` must be ARRAY, got INTEGER"}, + {`last([1, 2, 3])`, 3}, + {`last([])`, nil}, + {`last(1)`, "argument to `last` must be ARRAY, got INTEGER"}, + {`rest([1, 2, 3])`, []int{2, 3}}, + {`rest([])`, nil}, + {`push([], 1)`, []int{1}}, + {`push(1, 1)`, "argument to `push` must be ARRAY, got INTEGER"}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + + switch expected := tt.expected.(type) { + case int: + testIntegerObject(t, evaluated, int64(expected)) + case nil: + testNullObject(t, evaluated) + case string: + errObj, ok := evaluated.(*object.Error) + if !ok { + t.Errorf("object is not Error. got=%T (%+v)", + evaluated, evaluated) + continue + } + if errObj.Message != expected { + t.Errorf("wrong error message. expected=%q, got=%q", + expected, errObj.Message) + } + case []int: + array, ok := evaluated.(*object.Array) + if !ok { + t.Errorf("obj not Array. got=%T (%+v)", evaluated, evaluated) + continue + } + + if len(array.Elements) != len(expected) { + t.Errorf("wrong num of elements. want=%d, got=%d", + len(expected), len(array.Elements)) + continue + } + + for i, expectedElem := range expected { + testIntegerObject(t, array.Elements[i], int64(expectedElem)) + } + } + } +} + +func TestArrayLiterals(t *testing.T) { + input := "[1, 2 * 2, 3 + 3]" + + evaluated := testEval(input) + result, ok := evaluated.(*object.Array) + if !ok { + t.Fatalf("object is not Array. got=%T (%+v)", evaluated, evaluated) + } + + if len(result.Elements) != 3 { + t.Fatalf("array has wrong num of elements. got=%d", + len(result.Elements)) + } + + testIntegerObject(t, result.Elements[0], 1) + testIntegerObject(t, result.Elements[1], 4) + testIntegerObject(t, result.Elements[2], 6) +} + +func TestArrayIndexExpressions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + { + "[1, 2, 3][0]", + 1, + }, + { + "[1, 2, 3][1]", + 2, + }, + { + "[1, 2, 3][2]", + 3, + }, + { + "let i = 0; [1][i];", + 1, + }, + { + "[1, 2, 3][1 + 1];", + 3, + }, + { + "let myArray = [1, 2, 3]; myArray[2];", + 3, + }, + { + "let myArray = [1, 2, 3]; myArray[0] + myArray[1] + myArray[2];", + 6, + }, + { + "let myArray = [1, 2, 3]; let i = myArray[0]; myArray[i]", + 2, + }, + { + "[1, 2, 3][3]", + nil, + }, + { + "[1, 2, 3][-1]", + nil, + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + integer, ok := tt.expected.(int) + if ok { + testIntegerObject(t, evaluated, int64(integer)) + } else { + testNullObject(t, evaluated) + } + } +} + +func TestHashLiterals(t *testing.T) { + input := `let two = "two"; + { + "one": 10 - 9, + two: 1 + 1, + "thr" + "ee": 6 / 2, + 4: 4, + true: 5, + false: 6 + }` + + evaluated := testEval(input) + result, ok := evaluated.(*object.Hash) + if !ok { + t.Fatalf("Eval didn't return Hash. got=%T (%+v)", evaluated, evaluated) + } + + expected := map[object.HashKey]int64{ + (&object.String{Value: "one"}).HashKey(): 1, + (&object.String{Value: "two"}).HashKey(): 2, + (&object.String{Value: "three"}).HashKey(): 3, + (&object.Integer{Value: 4}).HashKey(): 4, + TRUE.HashKey(): 5, + FALSE.HashKey(): 6, + } + + if len(result.Pairs) != len(expected) { + t.Fatalf("Hash has wrong num of pairs. got=%d", len(result.Pairs)) + } + + for expectedKey, expectedValue := range expected { + pair, ok := result.Pairs[expectedKey] + if !ok { + t.Errorf("no pair for given key in Pairs") + } + + testIntegerObject(t, pair.Value, expectedValue) + } +} + +func TestHashIndexExpressions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + { + `{"foo": 5}["foo"]`, + 5, + }, + { + `{"foo": 5}["bar"]`, + nil, + }, + { + `let key = "foo"; {"foo": 5}[key]`, + 5, + }, + { + `{}["foo"]`, + nil, + }, + { + `{5: 5}[5]`, + 5, + }, + { + `{true: 5}[true]`, + 5, + }, + { + `{false: 5}[false]`, + 5, + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + integer, ok := tt.expected.(int) + if ok { + testIntegerObject(t, evaluated, int64(integer)) + } else { + testNullObject(t, evaluated) + } + } +} +func testEval(input string) object.Object { + l := lexer.New(input) + p := parser.New(l) + program := p.ParseProgram() + env := object.NewEnvironment() + + return Eval(program, env) +} + +func testIntegerObject(t *testing.T, obj object.Object, expected int64) bool { + result, ok := obj.(*object.Integer) + if !ok { + t.Errorf("object is not Integer. got=%T (%+v)", obj, obj) + return false + } + if result.Value != expected { + t.Errorf("object has wrong value. got=%d, want=%d", + result.Value, expected) + return false + } + + return true +} + +func testBooleanObject(t *testing.T, obj object.Object, expected bool) bool { + result, ok := obj.(*object.Boolean) + if !ok { + t.Errorf("object is not Boolean. got=%T (%+v)", obj, obj) + return false + } + if result.Value != expected { + t.Errorf("object has wrong value. got=%t, want=%t", + result.Value, expected) + return false + } + return true +} + +func testNullObject(t *testing.T, obj object.Object) bool { + if obj != NULL { + t.Errorf("object is not NULL. got=%T (%+v)", obj, obj) + return false + } + return true +} diff --git a/wcig_code_1_2/07/src/monkey/go.mod b/wcig_code_1_2/07/src/monkey/go.mod new file mode 100644 index 0000000..3064854 --- /dev/null +++ b/wcig_code_1_2/07/src/monkey/go.mod @@ -0,0 +1,3 @@ +module monkey + +go 1.14 diff --git a/wcig_code_1_2/07/src/monkey/lexer/lexer.go b/wcig_code_1_2/07/src/monkey/lexer/lexer.go new file mode 100644 index 0000000..db4f5f7 --- /dev/null +++ b/wcig_code_1_2/07/src/monkey/lexer/lexer.go @@ -0,0 +1,157 @@ +package lexer + +import "monkey/token" + +type Lexer struct { + input string + position int // current position in input (points to current char) + readPosition int // current reading position in input (after current char) + ch byte // current char under examination +} + +func New(input string) *Lexer { + l := &Lexer{input: input} + l.readChar() + return l +} + +func (l *Lexer) NextToken() token.Token { + var tok token.Token + + l.skipWhitespace() + + switch l.ch { + case '=': + if l.peekChar() == '=' { + ch := l.ch + l.readChar() + literal := string(ch) + string(l.ch) + tok = token.Token{Type: token.EQ, Literal: literal} + } else { + tok = newToken(token.ASSIGN, l.ch) + } + case '+': + tok = newToken(token.PLUS, l.ch) + case '-': + tok = newToken(token.MINUS, l.ch) + case '!': + if l.peekChar() == '=' { + ch := l.ch + l.readChar() + literal := string(ch) + string(l.ch) + tok = token.Token{Type: token.NOT_EQ, Literal: literal} + } else { + tok = newToken(token.BANG, l.ch) + } + case '/': + tok = newToken(token.SLASH, l.ch) + case '*': + tok = newToken(token.ASTERISK, l.ch) + case '<': + tok = newToken(token.LT, l.ch) + case '>': + tok = newToken(token.GT, l.ch) + case ';': + tok = newToken(token.SEMICOLON, l.ch) + case ':': + tok = newToken(token.COLON, l.ch) + case ',': + tok = newToken(token.COMMA, l.ch) + case '{': + tok = newToken(token.LBRACE, l.ch) + case '}': + tok = newToken(token.RBRACE, l.ch) + case '(': + tok = newToken(token.LPAREN, l.ch) + case ')': + tok = newToken(token.RPAREN, l.ch) + case '"': + tok.Type = token.STRING + tok.Literal = l.readString() + case '[': + tok = newToken(token.LBRACKET, l.ch) + case ']': + tok = newToken(token.RBRACKET, l.ch) + case 0: + tok.Literal = "" + tok.Type = token.EOF + default: + if isLetter(l.ch) { + tok.Literal = l.readIdentifier() + tok.Type = token.LookupIdent(tok.Literal) + return tok + } else if isDigit(l.ch) { + tok.Type = token.INT + tok.Literal = l.readNumber() + return tok + } else { + tok = newToken(token.ILLEGAL, l.ch) + } + } + + l.readChar() + return tok +} + +func (l *Lexer) skipWhitespace() { + for l.ch == ' ' || l.ch == '\t' || l.ch == '\n' || l.ch == '\r' { + l.readChar() + } +} + +func (l *Lexer) readChar() { + if l.readPosition >= len(l.input) { + l.ch = 0 + } else { + l.ch = l.input[l.readPosition] + } + l.position = l.readPosition + l.readPosition += 1 +} + +func (l *Lexer) peekChar() byte { + if l.readPosition >= len(l.input) { + return 0 + } else { + return l.input[l.readPosition] + } +} + +func (l *Lexer) readIdentifier() string { + position := l.position + for isLetter(l.ch) { + l.readChar() + } + return l.input[position:l.position] +} + +func (l *Lexer) readNumber() string { + position := l.position + for isDigit(l.ch) { + l.readChar() + } + return l.input[position:l.position] +} + +func (l *Lexer) readString() string { + position := l.position + 1 + for { + l.readChar() + if l.ch == '"' || l.ch == 0 { + break + } + } + return l.input[position:l.position] +} + +func isLetter(ch byte) bool { + return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' +} + +func isDigit(ch byte) bool { + return '0' <= ch && ch <= '9' +} + +func newToken(tokenType token.TokenType, ch byte) token.Token { + return token.Token{Type: tokenType, Literal: string(ch)} +} diff --git a/wcig_code_1_2/07/src/monkey/lexer/lexer_test.go b/wcig_code_1_2/07/src/monkey/lexer/lexer_test.go new file mode 100644 index 0000000..e4e64a4 --- /dev/null +++ b/wcig_code_1_2/07/src/monkey/lexer/lexer_test.go @@ -0,0 +1,143 @@ +package lexer + +import ( + "testing" + + "monkey/token" +) + +func TestNextToken(t *testing.T) { + input := `let five = 5; +let ten = 10; + +let add = fn(x, y) { + x + y; +}; + +let result = add(five, ten); +!-/*5; +5 < 10 > 5; + +if (5 < 10) { + return true; +} else { + return false; +} + +10 == 10; +10 != 9; +"foobar" +"foo bar" +[1, 2]; +{"foo": "bar"} +` + + tests := []struct { + expectedType token.TokenType + expectedLiteral string + }{ + {token.LET, "let"}, + {token.IDENT, "five"}, + {token.ASSIGN, "="}, + {token.INT, "5"}, + {token.SEMICOLON, ";"}, + {token.LET, "let"}, + {token.IDENT, "ten"}, + {token.ASSIGN, "="}, + {token.INT, "10"}, + {token.SEMICOLON, ";"}, + {token.LET, "let"}, + {token.IDENT, "add"}, + {token.ASSIGN, "="}, + {token.FUNCTION, "fn"}, + {token.LPAREN, "("}, + {token.IDENT, "x"}, + {token.COMMA, ","}, + {token.IDENT, "y"}, + {token.RPAREN, ")"}, + {token.LBRACE, "{"}, + {token.IDENT, "x"}, + {token.PLUS, "+"}, + {token.IDENT, "y"}, + {token.SEMICOLON, ";"}, + {token.RBRACE, "}"}, + {token.SEMICOLON, ";"}, + {token.LET, "let"}, + {token.IDENT, "result"}, + {token.ASSIGN, "="}, + {token.IDENT, "add"}, + {token.LPAREN, "("}, + {token.IDENT, "five"}, + {token.COMMA, ","}, + {token.IDENT, "ten"}, + {token.RPAREN, ")"}, + {token.SEMICOLON, ";"}, + {token.BANG, "!"}, + {token.MINUS, "-"}, + {token.SLASH, "/"}, + {token.ASTERISK, "*"}, + {token.INT, "5"}, + {token.SEMICOLON, ";"}, + {token.INT, "5"}, + {token.LT, "<"}, + {token.INT, "10"}, + {token.GT, ">"}, + {token.INT, "5"}, + {token.SEMICOLON, ";"}, + {token.IF, "if"}, + {token.LPAREN, "("}, + {token.INT, "5"}, + {token.LT, "<"}, + {token.INT, "10"}, + {token.RPAREN, ")"}, + {token.LBRACE, "{"}, + {token.RETURN, "return"}, + {token.TRUE, "true"}, + {token.SEMICOLON, ";"}, + {token.RBRACE, "}"}, + {token.ELSE, "else"}, + {token.LBRACE, "{"}, + {token.RETURN, "return"}, + {token.FALSE, "false"}, + {token.SEMICOLON, ";"}, + {token.RBRACE, "}"}, + {token.INT, "10"}, + {token.EQ, "=="}, + {token.INT, "10"}, + {token.SEMICOLON, ";"}, + {token.INT, "10"}, + {token.NOT_EQ, "!="}, + {token.INT, "9"}, + {token.SEMICOLON, ";"}, + {token.STRING, "foobar"}, + {token.STRING, "foo bar"}, + {token.LBRACKET, "["}, + {token.INT, "1"}, + {token.COMMA, ","}, + {token.INT, "2"}, + {token.RBRACKET, "]"}, + {token.SEMICOLON, ";"}, + {token.LBRACE, "{"}, + {token.STRING, "foo"}, + {token.COLON, ":"}, + {token.STRING, "bar"}, + {token.RBRACE, "}"}, + {token.EOF, ""}, + } + + l := New(input) + + for i, tt := range tests { + tok := l.NextToken() + + if tok.Type != tt.expectedType { + t.Fatalf("tests[%d] - tokentype wrong. expected=%q, got=%q", + i, tt.expectedType, tok.Type) + } + + if tok.Literal != tt.expectedLiteral { + t.Fatalf("tests[%d] - literal wrong. expected=%q, got=%q", + i, tt.expectedLiteral, tok.Literal) + } + } +} diff --git a/wcig_code_1_2/07/src/monkey/main.go b/wcig_code_1_2/07/src/monkey/main.go new file mode 100644 index 0000000..1d27138 --- /dev/null +++ b/wcig_code_1_2/07/src/monkey/main.go @@ -0,0 +1,19 @@ +package main + +import ( + "fmt" + "monkey/repl" + "os" + "os/user" +) + +func main() { + user, err := user.Current() + if err != nil { + panic(err) + } + fmt.Printf("Hello %s! This is the Monkey programming language!\n", + user.Username) + fmt.Printf("Feel free to type in commands\n") + repl.Start(os.Stdin, os.Stdout) +} diff --git a/wcig_code_1_2/07/src/monkey/object/environment.go b/wcig_code_1_2/07/src/monkey/object/environment.go new file mode 100644 index 0000000..6f31070 --- /dev/null +++ b/wcig_code_1_2/07/src/monkey/object/environment.go @@ -0,0 +1,30 @@ +package object + +func NewEnclosedEnvironment(outer *Environment) *Environment { + env := NewEnvironment() + env.outer = outer + return env +} + +func NewEnvironment() *Environment { + s := make(map[string]Object) + return &Environment{store: s, outer: nil} +} + +type Environment struct { + store map[string]Object + outer *Environment +} + +func (e *Environment) Get(name string) (Object, bool) { + obj, ok := e.store[name] + if !ok && e.outer != nil { + obj, ok = e.outer.Get(name) + } + return obj, ok +} + +func (e *Environment) Set(name string, val Object) Object { + e.store[name] = val + return val +} diff --git a/wcig_code_1_2/07/src/monkey/object/object.go b/wcig_code_1_2/07/src/monkey/object/object.go new file mode 100644 index 0000000..9bdb4f4 --- /dev/null +++ b/wcig_code_1_2/07/src/monkey/object/object.go @@ -0,0 +1,196 @@ +package object + +import ( + "bytes" + "fmt" + "hash/fnv" + "monkey/ast" + "monkey/code" + "strings" +) + +type BuiltinFunction func(args ...Object) Object + +type ObjectType string + +const ( + NULL_OBJ = "NULL" + ERROR_OBJ = "ERROR" + + INTEGER_OBJ = "INTEGER" + BOOLEAN_OBJ = "BOOLEAN" + STRING_OBJ = "STRING" + + RETURN_VALUE_OBJ = "RETURN_VALUE" + + FUNCTION_OBJ = "FUNCTION" + BUILTIN_OBJ = "BUILTIN" + + ARRAY_OBJ = "ARRAY" + HASH_OBJ = "HASH" + + COMPILED_FUNCTION_OBJ = "COMPILED_FUNCTION_OBJ" +) + +type HashKey struct { + Type ObjectType + Value uint64 +} + +type Hashable interface { + HashKey() HashKey +} + +type Object interface { + Type() ObjectType + Inspect() string +} + +type Integer struct { + Value int64 +} + +func (i *Integer) Type() ObjectType { return INTEGER_OBJ } +func (i *Integer) Inspect() string { return fmt.Sprintf("%d", i.Value) } +func (i *Integer) HashKey() HashKey { + return HashKey{Type: i.Type(), Value: uint64(i.Value)} +} + +type Boolean struct { + Value bool +} + +func (b *Boolean) Type() ObjectType { return BOOLEAN_OBJ } +func (b *Boolean) Inspect() string { return fmt.Sprintf("%t", b.Value) } +func (b *Boolean) HashKey() HashKey { + var value uint64 + + if b.Value { + value = 1 + } else { + value = 0 + } + + return HashKey{Type: b.Type(), Value: value} +} + +type Null struct{} + +func (n *Null) Type() ObjectType { return NULL_OBJ } +func (n *Null) Inspect() string { return "null" } + +type ReturnValue struct { + Value Object +} + +func (rv *ReturnValue) Type() ObjectType { return RETURN_VALUE_OBJ } +func (rv *ReturnValue) Inspect() string { return rv.Value.Inspect() } + +type Error struct { + Message string +} + +func (e *Error) Type() ObjectType { return ERROR_OBJ } +func (e *Error) Inspect() string { return "ERROR: " + e.Message } + +type Function struct { + Parameters []*ast.Identifier + Body *ast.BlockStatement + Env *Environment +} + +func (f *Function) Type() ObjectType { return FUNCTION_OBJ } +func (f *Function) Inspect() string { + var out bytes.Buffer + + params := []string{} + for _, p := range f.Parameters { + params = append(params, p.String()) + } + + out.WriteString("fn") + out.WriteString("(") + out.WriteString(strings.Join(params, ", ")) + out.WriteString(") {\n") + out.WriteString(f.Body.String()) + out.WriteString("\n}") + + return out.String() +} + +type String struct { + Value string +} + +func (s *String) Type() ObjectType { return STRING_OBJ } +func (s *String) Inspect() string { return s.Value } +func (s *String) HashKey() HashKey { + h := fnv.New64a() + h.Write([]byte(s.Value)) + + return HashKey{Type: s.Type(), Value: h.Sum64()} +} + +type Builtin struct { + Fn BuiltinFunction +} + +func (b *Builtin) Type() ObjectType { return BUILTIN_OBJ } +func (b *Builtin) Inspect() string { return "builtin function" } + +type Array struct { + Elements []Object +} + +func (ao *Array) Type() ObjectType { return ARRAY_OBJ } +func (ao *Array) Inspect() string { + var out bytes.Buffer + + elements := []string{} + for _, e := range ao.Elements { + elements = append(elements, e.Inspect()) + } + + out.WriteString("[") + out.WriteString(strings.Join(elements, ", ")) + out.WriteString("]") + + return out.String() +} + +type HashPair struct { + Key Object + Value Object +} + +type Hash struct { + Pairs map[HashKey]HashPair +} + +func (h *Hash) Type() ObjectType { return HASH_OBJ } +func (h *Hash) Inspect() string { + var out bytes.Buffer + + pairs := []string{} + for _, pair := range h.Pairs { + pairs = append(pairs, fmt.Sprintf("%s: %s", + pair.Key.Inspect(), pair.Value.Inspect())) + } + + out.WriteString("{") + out.WriteString(strings.Join(pairs, ", ")) + out.WriteString("}") + + return out.String() +} + +type CompiledFunction struct { + Instructions code.Instructions + NumLocals int + NumParameters int +} + +func (cf *CompiledFunction) Type() ObjectType { return COMPILED_FUNCTION_OBJ } +func (cf *CompiledFunction) Inspect() string { + return fmt.Sprintf("CompiledFunction[%p]", cf) +} diff --git a/wcig_code_1_2/07/src/monkey/object/object_test.go b/wcig_code_1_2/07/src/monkey/object/object_test.go new file mode 100644 index 0000000..63228a2 --- /dev/null +++ b/wcig_code_1_2/07/src/monkey/object/object_test.go @@ -0,0 +1,60 @@ +package object + +import "testing" + +func TestStringHashKey(t *testing.T) { + hello1 := &String{Value: "Hello World"} + hello2 := &String{Value: "Hello World"} + diff1 := &String{Value: "My name is johnny"} + diff2 := &String{Value: "My name is johnny"} + + if hello1.HashKey() != hello2.HashKey() { + t.Errorf("strings with same content have different hash keys") + } + + if diff1.HashKey() != diff2.HashKey() { + t.Errorf("strings with same content have different hash keys") + } + + if hello1.HashKey() == diff1.HashKey() { + t.Errorf("strings with different content have same hash keys") + } +} + +func TestBooleanHashKey(t *testing.T) { + true1 := &Boolean{Value: true} + true2 := &Boolean{Value: true} + false1 := &Boolean{Value: false} + false2 := &Boolean{Value: false} + + if true1.HashKey() != true2.HashKey() { + t.Errorf("trues do not have same hash key") + } + + if false1.HashKey() != false2.HashKey() { + t.Errorf("falses do not have same hash key") + } + + if true1.HashKey() == false1.HashKey() { + t.Errorf("true has same hash key as false") + } +} + +func TestIntegerHashKey(t *testing.T) { + one1 := &Integer{Value: 1} + one2 := &Integer{Value: 1} + two1 := &Integer{Value: 2} + two2 := &Integer{Value: 2} + + if one1.HashKey() != one2.HashKey() { + t.Errorf("integers with same content have twoerent hash keys") + } + + if two1.HashKey() != two2.HashKey() { + t.Errorf("integers with same content have twoerent hash keys") + } + + if one1.HashKey() == two1.HashKey() { + t.Errorf("integers with twoerent content have same hash keys") + } +} diff --git a/wcig_code_1_2/07/src/monkey/parser/parser.go b/wcig_code_1_2/07/src/monkey/parser/parser.go new file mode 100644 index 0000000..0a1f19c --- /dev/null +++ b/wcig_code_1_2/07/src/monkey/parser/parser.go @@ -0,0 +1,491 @@ +package parser + +import ( + "fmt" + "monkey/ast" + "monkey/lexer" + "monkey/token" + "strconv" +) + +const ( + _ int = iota + LOWEST + EQUALS // == + LESSGREATER // > or < + SUM // + + PRODUCT // * + PREFIX // -X or !X + CALL // myFunction(X) + INDEX // array[index] +) + +var precedences = map[token.TokenType]int{ + token.EQ: EQUALS, + token.NOT_EQ: EQUALS, + token.LT: LESSGREATER, + token.GT: LESSGREATER, + token.PLUS: SUM, + token.MINUS: SUM, + token.SLASH: PRODUCT, + token.ASTERISK: PRODUCT, + token.LPAREN: CALL, + token.LBRACKET: INDEX, +} + +type ( + prefixParseFn func() ast.Expression + infixParseFn func(ast.Expression) ast.Expression +) + +type Parser struct { + l *lexer.Lexer + errors []string + + curToken token.Token + peekToken token.Token + + prefixParseFns map[token.TokenType]prefixParseFn + infixParseFns map[token.TokenType]infixParseFn +} + +func New(l *lexer.Lexer) *Parser { + p := &Parser{ + l: l, + errors: []string{}, + } + + p.prefixParseFns = make(map[token.TokenType]prefixParseFn) + p.registerPrefix(token.IDENT, p.parseIdentifier) + p.registerPrefix(token.INT, p.parseIntegerLiteral) + p.registerPrefix(token.STRING, p.parseStringLiteral) + p.registerPrefix(token.BANG, p.parsePrefixExpression) + p.registerPrefix(token.MINUS, p.parsePrefixExpression) + p.registerPrefix(token.TRUE, p.parseBoolean) + p.registerPrefix(token.FALSE, p.parseBoolean) + p.registerPrefix(token.LPAREN, p.parseGroupedExpression) + p.registerPrefix(token.IF, p.parseIfExpression) + p.registerPrefix(token.FUNCTION, p.parseFunctionLiteral) + p.registerPrefix(token.LBRACKET, p.parseArrayLiteral) + p.registerPrefix(token.LBRACE, p.parseHashLiteral) + + p.infixParseFns = make(map[token.TokenType]infixParseFn) + p.registerInfix(token.PLUS, p.parseInfixExpression) + p.registerInfix(token.MINUS, p.parseInfixExpression) + p.registerInfix(token.SLASH, p.parseInfixExpression) + p.registerInfix(token.ASTERISK, p.parseInfixExpression) + p.registerInfix(token.EQ, p.parseInfixExpression) + p.registerInfix(token.NOT_EQ, p.parseInfixExpression) + p.registerInfix(token.LT, p.parseInfixExpression) + p.registerInfix(token.GT, p.parseInfixExpression) + + p.registerInfix(token.LPAREN, p.parseCallExpression) + p.registerInfix(token.LBRACKET, p.parseIndexExpression) + + // Read two tokens, so curToken and peekToken are both set + p.nextToken() + p.nextToken() + + return p +} + +func (p *Parser) nextToken() { + p.curToken = p.peekToken + p.peekToken = p.l.NextToken() +} + +func (p *Parser) curTokenIs(t token.TokenType) bool { + return p.curToken.Type == t +} + +func (p *Parser) peekTokenIs(t token.TokenType) bool { + return p.peekToken.Type == t +} + +func (p *Parser) expectPeek(t token.TokenType) bool { + if p.peekTokenIs(t) { + p.nextToken() + return true + } else { + p.peekError(t) + return false + } +} + +func (p *Parser) Errors() []string { + return p.errors +} + +func (p *Parser) peekError(t token.TokenType) { + msg := fmt.Sprintf("expected next token to be %s, got %s instead", + t, p.peekToken.Type) + p.errors = append(p.errors, msg) +} + +func (p *Parser) noPrefixParseFnError(t token.TokenType) { + msg := fmt.Sprintf("no prefix parse function for %s found", t) + p.errors = append(p.errors, msg) +} + +func (p *Parser) ParseProgram() *ast.Program { + program := &ast.Program{} + program.Statements = []ast.Statement{} + + for !p.curTokenIs(token.EOF) { + stmt := p.parseStatement() + if stmt != nil { + program.Statements = append(program.Statements, stmt) + } + p.nextToken() + } + + return program +} + +func (p *Parser) parseStatement() ast.Statement { + switch p.curToken.Type { + case token.LET: + return p.parseLetStatement() + case token.RETURN: + return p.parseReturnStatement() + default: + return p.parseExpressionStatement() + } +} + +func (p *Parser) parseLetStatement() *ast.LetStatement { + stmt := &ast.LetStatement{Token: p.curToken} + + if !p.expectPeek(token.IDENT) { + return nil + } + + stmt.Name = &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + + if !p.expectPeek(token.ASSIGN) { + return nil + } + + p.nextToken() + + stmt.Value = p.parseExpression(LOWEST) + + if p.peekTokenIs(token.SEMICOLON) { + p.nextToken() + } + + return stmt +} + +func (p *Parser) parseReturnStatement() *ast.ReturnStatement { + stmt := &ast.ReturnStatement{Token: p.curToken} + + p.nextToken() + + stmt.ReturnValue = p.parseExpression(LOWEST) + + if p.peekTokenIs(token.SEMICOLON) { + p.nextToken() + } + + return stmt +} + +func (p *Parser) parseExpressionStatement() *ast.ExpressionStatement { + stmt := &ast.ExpressionStatement{Token: p.curToken} + + stmt.Expression = p.parseExpression(LOWEST) + + if p.peekTokenIs(token.SEMICOLON) { + p.nextToken() + } + + return stmt +} + +func (p *Parser) parseExpression(precedence int) ast.Expression { + prefix := p.prefixParseFns[p.curToken.Type] + if prefix == nil { + p.noPrefixParseFnError(p.curToken.Type) + return nil + } + leftExp := prefix() + + for !p.peekTokenIs(token.SEMICOLON) && precedence < p.peekPrecedence() { + infix := p.infixParseFns[p.peekToken.Type] + if infix == nil { + return leftExp + } + + p.nextToken() + + leftExp = infix(leftExp) + } + + return leftExp +} + +func (p *Parser) peekPrecedence() int { + if p, ok := precedences[p.peekToken.Type]; ok { + return p + } + + return LOWEST +} + +func (p *Parser) curPrecedence() int { + if p, ok := precedences[p.curToken.Type]; ok { + return p + } + + return LOWEST +} + +func (p *Parser) parseIdentifier() ast.Expression { + return &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} +} + +func (p *Parser) parseIntegerLiteral() ast.Expression { + lit := &ast.IntegerLiteral{Token: p.curToken} + + value, err := strconv.ParseInt(p.curToken.Literal, 0, 64) + if err != nil { + msg := fmt.Sprintf("could not parse %q as integer", p.curToken.Literal) + p.errors = append(p.errors, msg) + return nil + } + + lit.Value = value + + return lit +} + +func (p *Parser) parseStringLiteral() ast.Expression { + return &ast.StringLiteral{Token: p.curToken, Value: p.curToken.Literal} +} + +func (p *Parser) parsePrefixExpression() ast.Expression { + expression := &ast.PrefixExpression{ + Token: p.curToken, + Operator: p.curToken.Literal, + } + + p.nextToken() + + expression.Right = p.parseExpression(PREFIX) + + return expression +} + +func (p *Parser) parseInfixExpression(left ast.Expression) ast.Expression { + expression := &ast.InfixExpression{ + Token: p.curToken, + Operator: p.curToken.Literal, + Left: left, + } + + precedence := p.curPrecedence() + p.nextToken() + expression.Right = p.parseExpression(precedence) + + return expression +} + +func (p *Parser) parseBoolean() ast.Expression { + return &ast.Boolean{Token: p.curToken, Value: p.curTokenIs(token.TRUE)} +} + +func (p *Parser) parseGroupedExpression() ast.Expression { + p.nextToken() + + exp := p.parseExpression(LOWEST) + + if !p.expectPeek(token.RPAREN) { + return nil + } + + return exp +} + +func (p *Parser) parseIfExpression() ast.Expression { + expression := &ast.IfExpression{Token: p.curToken} + + if !p.expectPeek(token.LPAREN) { + return nil + } + + p.nextToken() + expression.Condition = p.parseExpression(LOWEST) + + if !p.expectPeek(token.RPAREN) { + return nil + } + + if !p.expectPeek(token.LBRACE) { + return nil + } + + expression.Consequence = p.parseBlockStatement() + + if p.peekTokenIs(token.ELSE) { + p.nextToken() + + if !p.expectPeek(token.LBRACE) { + return nil + } + + expression.Alternative = p.parseBlockStatement() + } + + return expression +} + +func (p *Parser) parseBlockStatement() *ast.BlockStatement { + block := &ast.BlockStatement{Token: p.curToken} + block.Statements = []ast.Statement{} + + p.nextToken() + + for !p.curTokenIs(token.RBRACE) && !p.curTokenIs(token.EOF) { + stmt := p.parseStatement() + if stmt != nil { + block.Statements = append(block.Statements, stmt) + } + p.nextToken() + } + + return block +} + +func (p *Parser) parseFunctionLiteral() ast.Expression { + lit := &ast.FunctionLiteral{Token: p.curToken} + + if !p.expectPeek(token.LPAREN) { + return nil + } + + lit.Parameters = p.parseFunctionParameters() + + if !p.expectPeek(token.LBRACE) { + return nil + } + + lit.Body = p.parseBlockStatement() + + return lit +} + +func (p *Parser) parseFunctionParameters() []*ast.Identifier { + identifiers := []*ast.Identifier{} + + if p.peekTokenIs(token.RPAREN) { + p.nextToken() + return identifiers + } + + p.nextToken() + + ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + identifiers = append(identifiers, ident) + + for p.peekTokenIs(token.COMMA) { + p.nextToken() + p.nextToken() + ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + identifiers = append(identifiers, ident) + } + + if !p.expectPeek(token.RPAREN) { + return nil + } + + return identifiers +} + +func (p *Parser) parseCallExpression(function ast.Expression) ast.Expression { + exp := &ast.CallExpression{Token: p.curToken, Function: function} + exp.Arguments = p.parseExpressionList(token.RPAREN) + return exp +} + +func (p *Parser) parseExpressionList(end token.TokenType) []ast.Expression { + list := []ast.Expression{} + + if p.peekTokenIs(end) { + p.nextToken() + return list + } + + p.nextToken() + list = append(list, p.parseExpression(LOWEST)) + + for p.peekTokenIs(token.COMMA) { + p.nextToken() + p.nextToken() + list = append(list, p.parseExpression(LOWEST)) + } + + if !p.expectPeek(end) { + return nil + } + + return list +} + +func (p *Parser) parseArrayLiteral() ast.Expression { + array := &ast.ArrayLiteral{Token: p.curToken} + + array.Elements = p.parseExpressionList(token.RBRACKET) + + return array +} + +func (p *Parser) parseIndexExpression(left ast.Expression) ast.Expression { + exp := &ast.IndexExpression{Token: p.curToken, Left: left} + + p.nextToken() + exp.Index = p.parseExpression(LOWEST) + + if !p.expectPeek(token.RBRACKET) { + return nil + } + + return exp +} + +func (p *Parser) parseHashLiteral() ast.Expression { + hash := &ast.HashLiteral{Token: p.curToken} + hash.Pairs = make(map[ast.Expression]ast.Expression) + + for !p.peekTokenIs(token.RBRACE) { + p.nextToken() + key := p.parseExpression(LOWEST) + + if !p.expectPeek(token.COLON) { + return nil + } + + p.nextToken() + value := p.parseExpression(LOWEST) + + hash.Pairs[key] = value + + if !p.peekTokenIs(token.RBRACE) && !p.expectPeek(token.COMMA) { + return nil + } + } + + if !p.expectPeek(token.RBRACE) { + return nil + } + + return hash +} + +func (p *Parser) registerPrefix(tokenType token.TokenType, fn prefixParseFn) { + p.prefixParseFns[tokenType] = fn +} + +func (p *Parser) registerInfix(tokenType token.TokenType, fn infixParseFn) { + p.infixParseFns[tokenType] = fn +} diff --git a/wcig_code_1_2/07/src/monkey/parser/parser_test.go b/wcig_code_1_2/07/src/monkey/parser/parser_test.go new file mode 100644 index 0000000..674487d --- /dev/null +++ b/wcig_code_1_2/07/src/monkey/parser/parser_test.go @@ -0,0 +1,1083 @@ +package parser + +import ( + "fmt" + "monkey/ast" + "monkey/lexer" + "testing" +) + +func TestLetStatements(t *testing.T) { + tests := []struct { + input string + expectedIdentifier string + expectedValue interface{} + }{ + {"let x = 5;", "x", 5}, + {"let y = true;", "y", true}, + {"let foobar = y;", "foobar", "y"}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain 1 statements. got=%d", + len(program.Statements)) + } + + stmt := program.Statements[0] + if !testLetStatement(t, stmt, tt.expectedIdentifier) { + return + } + + val := stmt.(*ast.LetStatement).Value + if !testLiteralExpression(t, val, tt.expectedValue) { + return + } + } +} + +func TestReturnStatements(t *testing.T) { + tests := []struct { + input string + expectedValue interface{} + }{ + {"return 5;", 5}, + {"return true;", true}, + {"return foobar;", "foobar"}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain 1 statements. got=%d", + len(program.Statements)) + } + + stmt := program.Statements[0] + returnStmt, ok := stmt.(*ast.ReturnStatement) + if !ok { + t.Fatalf("stmt not *ast.returnStatement. got=%T", stmt) + } + if returnStmt.TokenLiteral() != "return" { + t.Fatalf("returnStmt.TokenLiteral not 'return', got %q", + returnStmt.TokenLiteral()) + } + if testLiteralExpression(t, returnStmt.ReturnValue, tt.expectedValue) { + return + } + } +} + +func TestIdentifierExpression(t *testing.T) { + input := "foobar;" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program has not enough statements. got=%d", + len(program.Statements)) + } + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + ident, ok := stmt.Expression.(*ast.Identifier) + if !ok { + t.Fatalf("exp not *ast.Identifier. got=%T", stmt.Expression) + } + if ident.Value != "foobar" { + t.Errorf("ident.Value not %s. got=%s", "foobar", ident.Value) + } + if ident.TokenLiteral() != "foobar" { + t.Errorf("ident.TokenLiteral not %s. got=%s", "foobar", + ident.TokenLiteral()) + } +} + +func TestIntegerLiteralExpression(t *testing.T) { + input := "5;" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program has not enough statements. got=%d", + len(program.Statements)) + } + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + literal, ok := stmt.Expression.(*ast.IntegerLiteral) + if !ok { + t.Fatalf("exp not *ast.IntegerLiteral. got=%T", stmt.Expression) + } + if literal.Value != 5 { + t.Errorf("literal.Value not %d. got=%d", 5, literal.Value) + } + if literal.TokenLiteral() != "5" { + t.Errorf("literal.TokenLiteral not %s. got=%s", "5", + literal.TokenLiteral()) + } +} + +func TestParsingPrefixExpressions(t *testing.T) { + prefixTests := []struct { + input string + operator string + value interface{} + }{ + {"!5;", "!", 5}, + {"-15;", "-", 15}, + {"!foobar;", "!", "foobar"}, + {"-foobar;", "-", "foobar"}, + {"!true;", "!", true}, + {"!false;", "!", false}, + } + + for _, tt := range prefixTests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.PrefixExpression) + if !ok { + t.Fatalf("stmt is not ast.PrefixExpression. got=%T", stmt.Expression) + } + if exp.Operator != tt.operator { + t.Fatalf("exp.Operator is not '%s'. got=%s", + tt.operator, exp.Operator) + } + if !testLiteralExpression(t, exp.Right, tt.value) { + return + } + } +} + +func TestParsingInfixExpressions(t *testing.T) { + infixTests := []struct { + input string + leftValue interface{} + operator string + rightValue interface{} + }{ + {"5 + 5;", 5, "+", 5}, + {"5 - 5;", 5, "-", 5}, + {"5 * 5;", 5, "*", 5}, + {"5 / 5;", 5, "/", 5}, + {"5 > 5;", 5, ">", 5}, + {"5 < 5;", 5, "<", 5}, + {"5 == 5;", 5, "==", 5}, + {"5 != 5;", 5, "!=", 5}, + {"foobar + barfoo;", "foobar", "+", "barfoo"}, + {"foobar - barfoo;", "foobar", "-", "barfoo"}, + {"foobar * barfoo;", "foobar", "*", "barfoo"}, + {"foobar / barfoo;", "foobar", "/", "barfoo"}, + {"foobar > barfoo;", "foobar", ">", "barfoo"}, + {"foobar < barfoo;", "foobar", "<", "barfoo"}, + {"foobar == barfoo;", "foobar", "==", "barfoo"}, + {"foobar != barfoo;", "foobar", "!=", "barfoo"}, + {"true == true", true, "==", true}, + {"true != false", true, "!=", false}, + {"false == false", false, "==", false}, + } + + for _, tt := range infixTests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + if !testInfixExpression(t, stmt.Expression, tt.leftValue, + tt.operator, tt.rightValue) { + return + } + } +} + +func TestOperatorPrecedenceParsing(t *testing.T) { + tests := []struct { + input string + expected string + }{ + { + "-a * b", + "((-a) * b)", + }, + { + "!-a", + "(!(-a))", + }, + { + "a + b + c", + "((a + b) + c)", + }, + { + "a + b - c", + "((a + b) - c)", + }, + { + "a * b * c", + "((a * b) * c)", + }, + { + "a * b / c", + "((a * b) / c)", + }, + { + "a + b / c", + "(a + (b / c))", + }, + { + "a + b * c + d / e - f", + "(((a + (b * c)) + (d / e)) - f)", + }, + { + "3 + 4; -5 * 5", + "(3 + 4)((-5) * 5)", + }, + { + "5 > 4 == 3 < 4", + "((5 > 4) == (3 < 4))", + }, + { + "5 < 4 != 3 > 4", + "((5 < 4) != (3 > 4))", + }, + { + "3 + 4 * 5 == 3 * 1 + 4 * 5", + "((3 + (4 * 5)) == ((3 * 1) + (4 * 5)))", + }, + { + "true", + "true", + }, + { + "false", + "false", + }, + { + "3 > 5 == false", + "((3 > 5) == false)", + }, + { + "3 < 5 == true", + "((3 < 5) == true)", + }, + { + "1 + (2 + 3) + 4", + "((1 + (2 + 3)) + 4)", + }, + { + "(5 + 5) * 2", + "((5 + 5) * 2)", + }, + { + "2 / (5 + 5)", + "(2 / (5 + 5))", + }, + { + "(5 + 5) * 2 * (5 + 5)", + "(((5 + 5) * 2) * (5 + 5))", + }, + { + "-(5 + 5)", + "(-(5 + 5))", + }, + { + "!(true == true)", + "(!(true == true))", + }, + { + "a + add(b * c) + d", + "((a + add((b * c))) + d)", + }, + { + "add(a, b, 1, 2 * 3, 4 + 5, add(6, 7 * 8))", + "add(a, b, 1, (2 * 3), (4 + 5), add(6, (7 * 8)))", + }, + { + "add(a + b + c * d / f + g)", + "add((((a + b) + ((c * d) / f)) + g))", + }, + { + "a * [1, 2, 3, 4][b * c] * d", + "((a * ([1, 2, 3, 4][(b * c)])) * d)", + }, + { + "add(a * b[2], b[1], 2 * [1, 2][1])", + "add((a * (b[2])), (b[1]), (2 * ([1, 2][1])))", + }, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + actual := program.String() + if actual != tt.expected { + t.Errorf("expected=%q, got=%q", tt.expected, actual) + } + } +} + +func TestBooleanExpression(t *testing.T) { + tests := []struct { + input string + expectedBoolean bool + }{ + {"true;", true}, + {"false;", false}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program has not enough statements. got=%d", + len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + boolean, ok := stmt.Expression.(*ast.Boolean) + if !ok { + t.Fatalf("exp not *ast.Boolean. got=%T", stmt.Expression) + } + if boolean.Value != tt.expectedBoolean { + t.Errorf("boolean.Value not %t. got=%t", tt.expectedBoolean, + boolean.Value) + } + } +} + +func TestIfExpression(t *testing.T) { + input := `if (x < y) { x }` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.IfExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.IfExpression. got=%T", + stmt.Expression) + } + + if !testInfixExpression(t, exp.Condition, "x", "<", "y") { + return + } + + if len(exp.Consequence.Statements) != 1 { + t.Errorf("consequence is not 1 statements. got=%d\n", + len(exp.Consequence.Statements)) + } + + consequence, ok := exp.Consequence.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", + exp.Consequence.Statements[0]) + } + + if !testIdentifier(t, consequence.Expression, "x") { + return + } + + if exp.Alternative != nil { + t.Errorf("exp.Alternative.Statements was not nil. got=%+v", exp.Alternative) + } +} + +func TestIfElseExpression(t *testing.T) { + input := `if (x < y) { x } else { y }` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.IfExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.IfExpression. got=%T", stmt.Expression) + } + + if !testInfixExpression(t, exp.Condition, "x", "<", "y") { + return + } + + if len(exp.Consequence.Statements) != 1 { + t.Errorf("consequence is not 1 statements. got=%d\n", + len(exp.Consequence.Statements)) + } + + consequence, ok := exp.Consequence.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", + exp.Consequence.Statements[0]) + } + + if !testIdentifier(t, consequence.Expression, "x") { + return + } + + if len(exp.Alternative.Statements) != 1 { + t.Errorf("exp.Alternative.Statements does not contain 1 statements. got=%d\n", + len(exp.Alternative.Statements)) + } + + alternative, ok := exp.Alternative.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", + exp.Alternative.Statements[0]) + } + + if !testIdentifier(t, alternative.Expression, "y") { + return + } +} + +func TestFunctionLiteralParsing(t *testing.T) { + input := `fn(x, y) { x + y; }` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + function, ok := stmt.Expression.(*ast.FunctionLiteral) + if !ok { + t.Fatalf("stmt.Expression is not ast.FunctionLiteral. got=%T", + stmt.Expression) + } + + if len(function.Parameters) != 2 { + t.Fatalf("function literal parameters wrong. want 2, got=%d\n", + len(function.Parameters)) + } + + testLiteralExpression(t, function.Parameters[0], "x") + testLiteralExpression(t, function.Parameters[1], "y") + + if len(function.Body.Statements) != 1 { + t.Fatalf("function.Body.Statements has not 1 statements. got=%d\n", + len(function.Body.Statements)) + } + + bodyStmt, ok := function.Body.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("function body stmt is not ast.ExpressionStatement. got=%T", + function.Body.Statements[0]) + } + + testInfixExpression(t, bodyStmt.Expression, "x", "+", "y") +} + +func TestFunctionParameterParsing(t *testing.T) { + tests := []struct { + input string + expectedParams []string + }{ + {input: "fn() {};", expectedParams: []string{}}, + {input: "fn(x) {};", expectedParams: []string{"x"}}, + {input: "fn(x, y, z) {};", expectedParams: []string{"x", "y", "z"}}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + function := stmt.Expression.(*ast.FunctionLiteral) + + if len(function.Parameters) != len(tt.expectedParams) { + t.Errorf("length parameters wrong. want %d, got=%d\n", + len(tt.expectedParams), len(function.Parameters)) + } + + for i, ident := range tt.expectedParams { + testLiteralExpression(t, function.Parameters[i], ident) + } + } +} + +func TestCallExpressionParsing(t *testing.T) { + input := "add(1, 2 * 3, 4 + 5);" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("stmt is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.CallExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.CallExpression. got=%T", + stmt.Expression) + } + + if !testIdentifier(t, exp.Function, "add") { + return + } + + if len(exp.Arguments) != 3 { + t.Fatalf("wrong length of arguments. got=%d", len(exp.Arguments)) + } + + testLiteralExpression(t, exp.Arguments[0], 1) + testInfixExpression(t, exp.Arguments[1], 2, "*", 3) + testInfixExpression(t, exp.Arguments[2], 4, "+", 5) +} + +func TestCallExpressionParameterParsing(t *testing.T) { + tests := []struct { + input string + expectedIdent string + expectedArgs []string + }{ + { + input: "add();", + expectedIdent: "add", + expectedArgs: []string{}, + }, + { + input: "add(1);", + expectedIdent: "add", + expectedArgs: []string{"1"}, + }, + { + input: "add(1, 2 * 3, 4 + 5);", + expectedIdent: "add", + expectedArgs: []string{"1", "(2 * 3)", "(4 + 5)"}, + }, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + exp, ok := stmt.Expression.(*ast.CallExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.CallExpression. got=%T", + stmt.Expression) + } + + if !testIdentifier(t, exp.Function, tt.expectedIdent) { + return + } + + if len(exp.Arguments) != len(tt.expectedArgs) { + t.Fatalf("wrong number of arguments. want=%d, got=%d", + len(tt.expectedArgs), len(exp.Arguments)) + } + + for i, arg := range tt.expectedArgs { + if exp.Arguments[i].String() != arg { + t.Errorf("argument %d wrong. want=%q, got=%q", i, + arg, exp.Arguments[i].String()) + } + } + } +} + +func TestStringLiteralExpression(t *testing.T) { + input := `"hello world";` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + literal, ok := stmt.Expression.(*ast.StringLiteral) + if !ok { + t.Fatalf("exp not *ast.StringLiteral. got=%T", stmt.Expression) + } + + if literal.Value != "hello world" { + t.Errorf("literal.Value not %q. got=%q", "hello world", literal.Value) + } +} + +func TestParsingEmptyArrayLiterals(t *testing.T) { + input := "[]" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + array, ok := stmt.Expression.(*ast.ArrayLiteral) + if !ok { + t.Fatalf("exp not ast.ArrayLiteral. got=%T", stmt.Expression) + } + + if len(array.Elements) != 0 { + t.Errorf("len(array.Elements) not 0. got=%d", len(array.Elements)) + } +} + +func TestParsingArrayLiterals(t *testing.T) { + input := "[1, 2 * 2, 3 + 3]" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + array, ok := stmt.Expression.(*ast.ArrayLiteral) + if !ok { + t.Fatalf("exp not ast.ArrayLiteral. got=%T", stmt.Expression) + } + + if len(array.Elements) != 3 { + t.Fatalf("len(array.Elements) not 3. got=%d", len(array.Elements)) + } + + testIntegerLiteral(t, array.Elements[0], 1) + testInfixExpression(t, array.Elements[1], 2, "*", 2) + testInfixExpression(t, array.Elements[2], 3, "+", 3) +} + +func TestParsingIndexExpressions(t *testing.T) { + input := "myArray[1 + 1]" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + indexExp, ok := stmt.Expression.(*ast.IndexExpression) + if !ok { + t.Fatalf("exp not *ast.IndexExpression. got=%T", stmt.Expression) + } + + if !testIdentifier(t, indexExp.Left, "myArray") { + return + } + + if !testInfixExpression(t, indexExp.Index, 1, "+", 1) { + return + } +} + +func TestParsingEmptyHashLiteral(t *testing.T) { + input := "{}" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + if len(hash.Pairs) != 0 { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } +} + +func TestParsingHashLiteralsStringKeys(t *testing.T) { + input := `{"one": 1, "two": 2, "three": 3}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + expected := map[string]int64{ + "one": 1, + "two": 2, + "three": 3, + } + + if len(hash.Pairs) != len(expected) { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } + + for key, value := range hash.Pairs { + literal, ok := key.(*ast.StringLiteral) + if !ok { + t.Errorf("key is not ast.StringLiteral. got=%T", key) + continue + } + + expectedValue := expected[literal.String()] + testIntegerLiteral(t, value, expectedValue) + } +} + +func TestParsingHashLiteralsBooleanKeys(t *testing.T) { + input := `{true: 1, false: 2}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + expected := map[string]int64{ + "true": 1, + "false": 2, + } + + if len(hash.Pairs) != len(expected) { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } + + for key, value := range hash.Pairs { + boolean, ok := key.(*ast.Boolean) + if !ok { + t.Errorf("key is not ast.BooleanLiteral. got=%T", key) + continue + } + + expectedValue := expected[boolean.String()] + testIntegerLiteral(t, value, expectedValue) + } +} + +func TestParsingHashLiteralsIntegerKeys(t *testing.T) { + input := `{1: 1, 2: 2, 3: 3}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + expected := map[string]int64{ + "1": 1, + "2": 2, + "3": 3, + } + + if len(hash.Pairs) != len(expected) { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } + + for key, value := range hash.Pairs { + integer, ok := key.(*ast.IntegerLiteral) + if !ok { + t.Errorf("key is not ast.IntegerLiteral. got=%T", key) + continue + } + + expectedValue := expected[integer.String()] + + testIntegerLiteral(t, value, expectedValue) + } +} + +func TestParsingHashLiteralsWithExpressions(t *testing.T) { + input := `{"one": 0 + 1, "two": 10 - 8, "three": 15 / 5}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + if len(hash.Pairs) != 3 { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } + + tests := map[string]func(ast.Expression){ + "one": func(e ast.Expression) { + testInfixExpression(t, e, 0, "+", 1) + }, + "two": func(e ast.Expression) { + testInfixExpression(t, e, 10, "-", 8) + }, + "three": func(e ast.Expression) { + testInfixExpression(t, e, 15, "/", 5) + }, + } + + for key, value := range hash.Pairs { + literal, ok := key.(*ast.StringLiteral) + if !ok { + t.Errorf("key is not ast.StringLiteral. got=%T", key) + continue + } + + testFunc, ok := tests[literal.String()] + if !ok { + t.Errorf("No test function for key %q found", literal.String()) + continue + } + + testFunc(value) + } +} + +func testLetStatement(t *testing.T, s ast.Statement, name string) bool { + if s.TokenLiteral() != "let" { + t.Errorf("s.TokenLiteral not 'let'. got=%q", s.TokenLiteral()) + return false + } + + letStmt, ok := s.(*ast.LetStatement) + if !ok { + t.Errorf("s not *ast.LetStatement. got=%T", s) + return false + } + + if letStmt.Name.Value != name { + t.Errorf("letStmt.Name.Value not '%s'. got=%s", name, letStmt.Name.Value) + return false + } + + if letStmt.Name.TokenLiteral() != name { + t.Errorf("letStmt.Name.TokenLiteral() not '%s'. got=%s", + name, letStmt.Name.TokenLiteral()) + return false + } + + return true +} + +func testInfixExpression(t *testing.T, exp ast.Expression, left interface{}, + operator string, right interface{}) bool { + + opExp, ok := exp.(*ast.InfixExpression) + if !ok { + t.Errorf("exp is not ast.InfixExpression. got=%T(%s)", exp, exp) + return false + } + + if !testLiteralExpression(t, opExp.Left, left) { + return false + } + + if opExp.Operator != operator { + t.Errorf("exp.Operator is not '%s'. got=%q", operator, opExp.Operator) + return false + } + + if !testLiteralExpression(t, opExp.Right, right) { + return false + } + + return true +} + +func testLiteralExpression( + t *testing.T, + exp ast.Expression, + expected interface{}, +) bool { + switch v := expected.(type) { + case int: + return testIntegerLiteral(t, exp, int64(v)) + case int64: + return testIntegerLiteral(t, exp, v) + case string: + return testIdentifier(t, exp, v) + case bool: + return testBooleanLiteral(t, exp, v) + } + t.Errorf("type of exp not handled. got=%T", exp) + return false +} + +func testIntegerLiteral(t *testing.T, il ast.Expression, value int64) bool { + integ, ok := il.(*ast.IntegerLiteral) + if !ok { + t.Errorf("il not *ast.IntegerLiteral. got=%T", il) + return false + } + + if integ.Value != value { + t.Errorf("integ.Value not %d. got=%d", value, integ.Value) + return false + } + + if integ.TokenLiteral() != fmt.Sprintf("%d", value) { + t.Errorf("integ.TokenLiteral not %d. got=%s", value, + integ.TokenLiteral()) + return false + } + + return true +} + +func testIdentifier(t *testing.T, exp ast.Expression, value string) bool { + ident, ok := exp.(*ast.Identifier) + if !ok { + t.Errorf("exp not *ast.Identifier. got=%T", exp) + return false + } + + if ident.Value != value { + t.Errorf("ident.Value not %s. got=%s", value, ident.Value) + return false + } + + if ident.TokenLiteral() != value { + t.Errorf("ident.TokenLiteral not %s. got=%s", value, + ident.TokenLiteral()) + return false + } + + return true +} + +func testBooleanLiteral(t *testing.T, exp ast.Expression, value bool) bool { + bo, ok := exp.(*ast.Boolean) + if !ok { + t.Errorf("exp not *ast.Boolean. got=%T", exp) + return false + } + + if bo.Value != value { + t.Errorf("bo.Value not %t. got=%t", value, bo.Value) + return false + } + + if bo.TokenLiteral() != fmt.Sprintf("%t", value) { + t.Errorf("bo.TokenLiteral not %t. got=%s", + value, bo.TokenLiteral()) + return false + } + + return true +} + +func checkParserErrors(t *testing.T, p *Parser) { + errors := p.Errors() + if len(errors) == 0 { + return + } + + t.Errorf("parser has %d errors", len(errors)) + for _, msg := range errors { + t.Errorf("parser error: %q", msg) + } + t.FailNow() +} diff --git a/wcig_code_1_2/07/src/monkey/parser/parser_tracing.go b/wcig_code_1_2/07/src/monkey/parser/parser_tracing.go new file mode 100644 index 0000000..5fc569b --- /dev/null +++ b/wcig_code_1_2/07/src/monkey/parser/parser_tracing.go @@ -0,0 +1,32 @@ +package parser + +import ( + "fmt" + "strings" +) + +var traceLevel int = 0 + +const traceIdentPlaceholder string = "\t" + +func identLevel() string { + return strings.Repeat(traceIdentPlaceholder, traceLevel-1) +} + +func tracePrint(fs string) { + fmt.Printf("%s%s\n", identLevel(), fs) +} + +func incIdent() { traceLevel = traceLevel + 1 } +func decIdent() { traceLevel = traceLevel - 1 } + +func trace(msg string) string { + incIdent() + tracePrint("BEGIN " + msg) + return msg +} + +func untrace(msg string) { + tracePrint("END " + msg) + decIdent() +} diff --git a/wcig_code_1_2/07/src/monkey/repl/repl.go b/wcig_code_1_2/07/src/monkey/repl/repl.go new file mode 100644 index 0000000..d14f36b --- /dev/null +++ b/wcig_code_1_2/07/src/monkey/repl/repl.go @@ -0,0 +1,83 @@ +package repl + +import ( + "bufio" + "fmt" + "io" + "monkey/compiler" + "monkey/lexer" + "monkey/object" + "monkey/parser" + "monkey/vm" +) + +const PROMPT = ">> " + +func Start(in io.Reader, out io.Writer) { + scanner := bufio.NewScanner(in) + + constants := []object.Object{} + globals := make([]object.Object, vm.GlobalsSize) + symbolTable := compiler.NewSymbolTable() + + for { + fmt.Fprintf(out, PROMPT) + scanned := scanner.Scan() + if !scanned { + return + } + + line := scanner.Text() + l := lexer.New(line) + p := parser.New(l) + + program := p.ParseProgram() + if len(p.Errors()) != 0 { + printParserErrors(out, p.Errors()) + continue + } + + comp := compiler.NewWithState(symbolTable, constants) + err := comp.Compile(program) + if err != nil { + fmt.Fprintf(out, "Woops! Compilation failed:\n %s\n", err) + continue + } + + code := comp.Bytecode() + constants = code.Constants + + machine := vm.NewWithGlobalsStore(code, globals) + err = machine.Run() + if err != nil { + fmt.Fprintf(out, "Woops! Executing bytecode failed:\n %s\n", err) + continue + } + + lastPopped := machine.LastPoppedStackElem() + io.WriteString(out, lastPopped.Inspect()) + io.WriteString(out, "\n") + } +} + +const MONKEY_FACE = ` __,__ + .--. .-" "-. .--. + / .. \/ .-. .-. \/ .. \ + | | '| / Y \ |' | | + | \ \ \ 0 | 0 / / / | + \ '- ,\.-"""""""-./, -' / + ''-' /_ ^ ^ _\ '-'' + | \._ _./ | + \ \ '~' / / + '._ '-=-' _.' + '-----' +` + +func printParserErrors(out io.Writer, errors []string) { + io.WriteString(out, MONKEY_FACE) + io.WriteString(out, "Woops! We ran into some monkey business here!\n") + io.WriteString(out, " parser errors:\n") + for _, msg := range errors { + io.WriteString(out, "\t"+msg+"\n") + } +} diff --git a/wcig_code_1_2/07/src/monkey/token/token.go b/wcig_code_1_2/07/src/monkey/token/token.go new file mode 100644 index 0000000..3d2d2f7 --- /dev/null +++ b/wcig_code_1_2/07/src/monkey/token/token.go @@ -0,0 +1,70 @@ +package token + +type TokenType string + +const ( + ILLEGAL = "ILLEGAL" + EOF = "EOF" + + // Identifiers + literals + IDENT = "IDENT" // add, foobar, x, y, ... + INT = "INT" // 1343456 + STRING = "STRING" // "foobar" + + // Operators + ASSIGN = "=" + PLUS = "+" + MINUS = "-" + BANG = "!" + ASTERISK = "*" + SLASH = "/" + + LT = "<" + GT = ">" + + EQ = "==" + NOT_EQ = "!=" + + // Delimiters + COMMA = "," + SEMICOLON = ";" + COLON = ":" + + LPAREN = "(" + RPAREN = ")" + LBRACE = "{" + RBRACE = "}" + LBRACKET = "[" + RBRACKET = "]" + + // Keywords + FUNCTION = "FUNCTION" + LET = "LET" + TRUE = "TRUE" + FALSE = "FALSE" + IF = "IF" + ELSE = "ELSE" + RETURN = "RETURN" +) + +type Token struct { + Type TokenType + Literal string +} + +var keywords = map[string]TokenType{ + "fn": FUNCTION, + "let": LET, + "true": TRUE, + "false": FALSE, + "if": IF, + "else": ELSE, + "return": RETURN, +} + +func LookupIdent(ident string) TokenType { + if tok, ok := keywords[ident]; ok { + return tok + } + return IDENT +} diff --git a/wcig_code_1_2/07/src/monkey/vm/frame.go b/wcig_code_1_2/07/src/monkey/vm/frame.go new file mode 100644 index 0000000..0fa415b --- /dev/null +++ b/wcig_code_1_2/07/src/monkey/vm/frame.go @@ -0,0 +1,26 @@ +package vm + +import ( + "monkey/code" + "monkey/object" +) + +type Frame struct { + fn *object.CompiledFunction + ip int + basePointer int +} + +func NewFrame(fn *object.CompiledFunction, basePointer int) *Frame { + f := &Frame{ + fn: fn, + ip: -1, + basePointer: basePointer, + } + + return f +} + +func (f *Frame) Instructions() code.Instructions { + return f.fn.Instructions +} diff --git a/wcig_code_1_2/07/src/monkey/vm/vm.go b/wcig_code_1_2/07/src/monkey/vm/vm.go new file mode 100644 index 0000000..f60e3bb --- /dev/null +++ b/wcig_code_1_2/07/src/monkey/vm/vm.go @@ -0,0 +1,503 @@ +package vm + +import ( + "fmt" + "monkey/code" + "monkey/compiler" + "monkey/object" +) + +const StackSize = 2048 +const GlobalsSize = 65536 +const MaxFrames = 1024 + +var True = &object.Boolean{Value: true} +var False = &object.Boolean{Value: false} +var Null = &object.Null{} + +type VM struct { + constants []object.Object + + stack []object.Object + sp int // Always points to the next value. Top of stack is stack[sp-1] + + globals []object.Object + + frames []*Frame + framesIndex int +} + +func New(bytecode *compiler.Bytecode) *VM { + mainFn := &object.CompiledFunction{Instructions: bytecode.Instructions} + mainFrame := NewFrame(mainFn, 0) + + frames := make([]*Frame, MaxFrames) + frames[0] = mainFrame + + return &VM{ + constants: bytecode.Constants, + + stack: make([]object.Object, StackSize), + sp: 0, + + globals: make([]object.Object, GlobalsSize), + + frames: frames, + framesIndex: 1, + } +} + +func NewWithGlobalsStore(bytecode *compiler.Bytecode, s []object.Object) *VM { + vm := New(bytecode) + vm.globals = s + return vm +} + +func (vm *VM) LastPoppedStackElem() object.Object { + return vm.stack[vm.sp] +} + +func (vm *VM) Run() error { + var ip int + var ins code.Instructions + var op code.Opcode + + for vm.currentFrame().ip < len(vm.currentFrame().Instructions())-1 { + vm.currentFrame().ip++ + + ip = vm.currentFrame().ip + ins = vm.currentFrame().Instructions() + op = code.Opcode(ins[ip]) + + switch op { + case code.OpConstant: + constIndex := code.ReadUint16(ins[ip+1:]) + vm.currentFrame().ip += 2 + + err := vm.push(vm.constants[constIndex]) + if err != nil { + return err + } + + case code.OpPop: + vm.pop() + + case code.OpAdd, code.OpSub, code.OpMul, code.OpDiv: + err := vm.executeBinaryOperation(op) + if err != nil { + return err + } + + case code.OpTrue: + err := vm.push(True) + if err != nil { + return err + } + + case code.OpFalse: + err := vm.push(False) + if err != nil { + return err + } + + case code.OpEqual, code.OpNotEqual, code.OpGreaterThan: + err := vm.executeComparison(op) + if err != nil { + return err + } + + case code.OpBang: + err := vm.executeBangOperator() + if err != nil { + return err + } + + case code.OpMinus: + err := vm.executeMinusOperator() + if err != nil { + return err + } + + case code.OpJump: + pos := int(code.ReadUint16(ins[ip+1:])) + vm.currentFrame().ip = pos - 1 + + case code.OpJumpNotTruthy: + pos := int(code.ReadUint16(ins[ip+1:])) + vm.currentFrame().ip += 2 + + condition := vm.pop() + if !isTruthy(condition) { + vm.currentFrame().ip = pos - 1 + } + + case code.OpNull: + err := vm.push(Null) + if err != nil { + return err + } + + case code.OpSetGlobal: + globalIndex := code.ReadUint16(ins[ip+1:]) + vm.currentFrame().ip += 2 + + vm.globals[globalIndex] = vm.pop() + + case code.OpGetGlobal: + globalIndex := code.ReadUint16(ins[ip+1:]) + vm.currentFrame().ip += 2 + + err := vm.push(vm.globals[globalIndex]) + if err != nil { + return err + } + + case code.OpArray: + numElements := int(code.ReadUint16(ins[ip+1:])) + vm.currentFrame().ip += 2 + + array := vm.buildArray(vm.sp-numElements, vm.sp) + vm.sp = vm.sp - numElements + + err := vm.push(array) + if err != nil { + return err + } + + case code.OpHash: + numElements := int(code.ReadUint16(ins[ip+1:])) + vm.currentFrame().ip += 2 + + hash, err := vm.buildHash(vm.sp-numElements, vm.sp) + if err != nil { + return err + } + vm.sp = vm.sp - numElements + + err = vm.push(hash) + if err != nil { + return err + } + + case code.OpIndex: + index := vm.pop() + left := vm.pop() + + err := vm.executeIndexExpression(left, index) + if err != nil { + return err + } + + case code.OpCall: + numArgs := code.ReadUint8(ins[ip+1:]) + vm.currentFrame().ip += 1 + + err := vm.callFunction(int(numArgs)) + if err != nil { + return err + } + + case code.OpReturnValue: + returnValue := vm.pop() + + frame := vm.popFrame() + vm.sp = frame.basePointer - 1 + + err := vm.push(returnValue) + if err != nil { + return err + } + + case code.OpReturn: + frame := vm.popFrame() + vm.sp = frame.basePointer - 1 + + err := vm.push(Null) + if err != nil { + return err + } + + case code.OpSetLocal: + localIndex := code.ReadUint8(ins[ip+1:]) + vm.currentFrame().ip += 1 + + frame := vm.currentFrame() + + vm.stack[frame.basePointer+int(localIndex)] = vm.pop() + + case code.OpGetLocal: + localIndex := code.ReadUint8(ins[ip+1:]) + vm.currentFrame().ip += 1 + + frame := vm.currentFrame() + + err := vm.push(vm.stack[frame.basePointer+int(localIndex)]) + if err != nil { + return err + } + } + } + + return nil +} + +func (vm *VM) push(o object.Object) error { + if vm.sp >= StackSize { + return fmt.Errorf("stack overflow") + } + + vm.stack[vm.sp] = o + vm.sp++ + + return nil +} + +func (vm *VM) pop() object.Object { + o := vm.stack[vm.sp-1] + vm.sp-- + return o +} + +func (vm *VM) executeBinaryOperation(op code.Opcode) error { + right := vm.pop() + left := vm.pop() + + leftType := left.Type() + rightType := right.Type() + + switch { + case leftType == object.INTEGER_OBJ && rightType == object.INTEGER_OBJ: + return vm.executeBinaryIntegerOperation(op, left, right) + case leftType == object.STRING_OBJ && rightType == object.STRING_OBJ: + return vm.executeBinaryStringOperation(op, left, right) + default: + return fmt.Errorf("unsupported types for binary operation: %s %s", + leftType, rightType) + } +} + +func (vm *VM) executeBinaryIntegerOperation( + op code.Opcode, + left, right object.Object, +) error { + leftValue := left.(*object.Integer).Value + rightValue := right.(*object.Integer).Value + + var result int64 + + switch op { + case code.OpAdd: + result = leftValue + rightValue + case code.OpSub: + result = leftValue - rightValue + case code.OpMul: + result = leftValue * rightValue + case code.OpDiv: + result = leftValue / rightValue + default: + return fmt.Errorf("unknown integer operator: %d", op) + } + + return vm.push(&object.Integer{Value: result}) +} + +func (vm *VM) executeComparison(op code.Opcode) error { + right := vm.pop() + left := vm.pop() + + if left.Type() == object.INTEGER_OBJ && right.Type() == object.INTEGER_OBJ { + return vm.executeIntegerComparison(op, left, right) + } + + switch op { + case code.OpEqual: + return vm.push(nativeBoolToBooleanObject(right == left)) + case code.OpNotEqual: + return vm.push(nativeBoolToBooleanObject(right != left)) + default: + return fmt.Errorf("unknown operator: %d (%s %s)", + op, left.Type(), right.Type()) + } +} + +func (vm *VM) executeIntegerComparison( + op code.Opcode, + left, right object.Object, +) error { + leftValue := left.(*object.Integer).Value + rightValue := right.(*object.Integer).Value + + switch op { + case code.OpEqual: + return vm.push(nativeBoolToBooleanObject(rightValue == leftValue)) + case code.OpNotEqual: + return vm.push(nativeBoolToBooleanObject(rightValue != leftValue)) + case code.OpGreaterThan: + return vm.push(nativeBoolToBooleanObject(leftValue > rightValue)) + default: + return fmt.Errorf("unknown operator: %d", op) + } +} + +func (vm *VM) executeBangOperator() error { + operand := vm.pop() + + switch operand { + case True: + return vm.push(False) + case False: + return vm.push(True) + case Null: + return vm.push(True) + default: + return vm.push(False) + } +} + +func (vm *VM) executeMinusOperator() error { + operand := vm.pop() + + if operand.Type() != object.INTEGER_OBJ { + return fmt.Errorf("unsupported type for negation: %s", operand.Type()) + } + + value := operand.(*object.Integer).Value + return vm.push(&object.Integer{Value: -value}) +} + +func (vm *VM) executeBinaryStringOperation( + op code.Opcode, + left, right object.Object, +) error { + if op != code.OpAdd { + return fmt.Errorf("unknown string operator: %d", op) + } + + leftValue := left.(*object.String).Value + rightValue := right.(*object.String).Value + + return vm.push(&object.String{Value: leftValue + rightValue}) +} + +func (vm *VM) buildArray(startIndex, endIndex int) object.Object { + elements := make([]object.Object, endIndex-startIndex) + + for i := startIndex; i < endIndex; i++ { + elements[i-startIndex] = vm.stack[i] + } + + return &object.Array{Elements: elements} +} + +func (vm *VM) buildHash(startIndex, endIndex int) (object.Object, error) { + hashedPairs := make(map[object.HashKey]object.HashPair) + + for i := startIndex; i < endIndex; i += 2 { + key := vm.stack[i] + value := vm.stack[i+1] + + pair := object.HashPair{Key: key, Value: value} + + hashKey, ok := key.(object.Hashable) + if !ok { + return nil, fmt.Errorf("unusable as hash key: %s", key.Type()) + } + + hashedPairs[hashKey.HashKey()] = pair + } + + return &object.Hash{Pairs: hashedPairs}, nil +} + +func (vm *VM) executeIndexExpression(left, index object.Object) error { + switch { + case left.Type() == object.ARRAY_OBJ && index.Type() == object.INTEGER_OBJ: + return vm.executeArrayIndex(left, index) + case left.Type() == object.HASH_OBJ: + return vm.executeHashIndex(left, index) + default: + return fmt.Errorf("index operator not supported: %s", left.Type()) + } +} + +func (vm *VM) executeArrayIndex(array, index object.Object) error { + arrayObject := array.(*object.Array) + i := index.(*object.Integer).Value + max := int64(len(arrayObject.Elements) - 1) + + if i < 0 || i > max { + return vm.push(Null) + } + + return vm.push(arrayObject.Elements[i]) +} + +func (vm *VM) executeHashIndex(hash, index object.Object) error { + hashObject := hash.(*object.Hash) + + key, ok := index.(object.Hashable) + if !ok { + return fmt.Errorf("unusable as hash key: %s", index.Type()) + } + + pair, ok := hashObject.Pairs[key.HashKey()] + if !ok { + return vm.push(Null) + } + + return vm.push(pair.Value) +} + +func (vm *VM) currentFrame() *Frame { + return vm.frames[vm.framesIndex-1] +} + +func (vm *VM) pushFrame(f *Frame) { + vm.frames[vm.framesIndex] = f + vm.framesIndex++ +} + +func (vm *VM) popFrame() *Frame { + vm.framesIndex-- + return vm.frames[vm.framesIndex] +} + +func (vm *VM) callFunction(numArgs int) error { + fn, ok := vm.stack[vm.sp-1-numArgs].(*object.CompiledFunction) + if !ok { + return fmt.Errorf("calling non-function") + } + + if numArgs != fn.NumParameters { + return fmt.Errorf("wrong number of arguments: want=%d, got=%d", + fn.NumParameters, numArgs) + } + + frame := NewFrame(fn, vm.sp-numArgs) + vm.pushFrame(frame) + + vm.sp = frame.basePointer + fn.NumLocals + + return nil +} + +func nativeBoolToBooleanObject(input bool) *object.Boolean { + if input { + return True + } + return False +} + +func isTruthy(obj object.Object) bool { + switch obj := obj.(type) { + + case *object.Boolean: + return obj.Value + + case *object.Null: + return false + + default: + return true + } +} diff --git a/wcig_code_1_2/07/src/monkey/vm/vm_test.go b/wcig_code_1_2/07/src/monkey/vm/vm_test.go new file mode 100644 index 0000000..cbb4fc8 --- /dev/null +++ b/wcig_code_1_2/07/src/monkey/vm/vm_test.go @@ -0,0 +1,573 @@ +package vm + +import ( + "fmt" + "monkey/ast" + "monkey/compiler" + "monkey/lexer" + "monkey/object" + "monkey/parser" + "testing" +) + +func TestIntegerArithmetic(t *testing.T) { + tests := []vmTestCase{ + {"1", 1}, + {"2", 2}, + {"1 + 2", 3}, + {"1 - 2", -1}, + {"1 * 2", 2}, + {"4 / 2", 2}, + {"50 / 2 * 2 + 10 - 5", 55}, + {"5 * (2 + 10)", 60}, + {"5 + 5 + 5 + 5 - 10", 10}, + {"2 * 2 * 2 * 2 * 2", 32}, + {"5 * 2 + 10", 20}, + {"5 + 2 * 10", 25}, + {"5 * (2 + 10)", 60}, + {"-5", -5}, + {"-10", -10}, + {"-50 + 100 + -50", 0}, + {"(5 + 10 * 2 + 15 / 3) * 2 + -10", 50}, + } + + runVmTests(t, tests) +} + +func TestBooleanExpressions(t *testing.T) { + tests := []vmTestCase{ + {"true", true}, + {"false", false}, + {"1 < 2", true}, + {"1 > 2", false}, + {"1 < 1", false}, + {"1 > 1", false}, + {"1 == 1", true}, + {"1 != 1", false}, + {"1 == 2", false}, + {"1 != 2", true}, + {"true == true", true}, + {"false == false", true}, + {"true == false", false}, + {"true != false", true}, + {"false != true", true}, + {"(1 < 2) == true", true}, + {"(1 < 2) == false", false}, + {"(1 > 2) == true", false}, + {"(1 > 2) == false", true}, + {"!true", false}, + {"!false", true}, + {"!5", false}, + {"!!true", true}, + {"!!false", false}, + {"!!5", true}, + {"!(if (false) { 5; })", true}, + } + + runVmTests(t, tests) +} + +func TestConditionals(t *testing.T) { + tests := []vmTestCase{ + {"if (true) { 10 }", 10}, + {"if (true) { 10 } else { 20 }", 10}, + {"if (false) { 10 } else { 20 } ", 20}, + {"if (1) { 10 }", 10}, + {"if (1 < 2) { 10 }", 10}, + {"if (1 < 2) { 10 } else { 20 }", 10}, + {"if (1 > 2) { 10 } else { 20 }", 20}, + {"if (1 > 2) { 10 }", Null}, + {"if (false) { 10 }", Null}, + {"if ((if (false) { 10 })) { 10 } else { 20 }", 20}, + } + + runVmTests(t, tests) +} + +func TestGlobalLetStatements(t *testing.T) { + tests := []vmTestCase{ + {"let one = 1; one", 1}, + {"let one = 1; let two = 2; one + two", 3}, + {"let one = 1; let two = one + one; one + two", 3}, + } + + runVmTests(t, tests) +} + +func TestStringExpressions(t *testing.T) { + tests := []vmTestCase{ + {`"monkey"`, "monkey"}, + {`"mon" + "key"`, "monkey"}, + {`"mon" + "key" + "banana"`, "monkeybanana"}, + } + + runVmTests(t, tests) +} + +func TestArrayLiterals(t *testing.T) { + tests := []vmTestCase{ + {"[]", []int{}}, + {"[1, 2, 3]", []int{1, 2, 3}}, + {"[1 + 2, 3 * 4, 5 + 6]", []int{3, 12, 11}}, + } + + runVmTests(t, tests) +} + +func TestHashLiterals(t *testing.T) { + tests := []vmTestCase{ + { + "{}", map[object.HashKey]int64{}, + }, + { + "{1: 2, 2: 3}", + map[object.HashKey]int64{ + (&object.Integer{Value: 1}).HashKey(): 2, + (&object.Integer{Value: 2}).HashKey(): 3, + }, + }, + { + "{1 + 1: 2 * 2, 3 + 3: 4 * 4}", + map[object.HashKey]int64{ + (&object.Integer{Value: 2}).HashKey(): 4, + (&object.Integer{Value: 6}).HashKey(): 16, + }, + }, + } + + runVmTests(t, tests) +} + +func TestIndexExpressions(t *testing.T) { + tests := []vmTestCase{ + {"[1, 2, 3][1]", 2}, + {"[1, 2, 3][0 + 2]", 3}, + {"[[1, 1, 1]][0][0]", 1}, + {"[][0]", Null}, + {"[1, 2, 3][99]", Null}, + {"[1][-1]", Null}, + {"{1: 1, 2: 2}[1]", 1}, + {"{1: 1, 2: 2}[2]", 2}, + {"{1: 1}[0]", Null}, + {"{}[0]", Null}, + } + + runVmTests(t, tests) +} + +func TestCallingFunctionsWithoutArguments(t *testing.T) { + tests := []vmTestCase{ + { + input: ` + let fivePlusTen = fn() { 5 + 10; }; + fivePlusTen(); + `, + expected: 15, + }, + { + input: ` + let one = fn() { 1; }; + let two = fn() { 2; }; + one() + two() + `, + expected: 3, + }, + { + input: ` + let a = fn() { 1 }; + let b = fn() { a() + 1 }; + let c = fn() { b() + 1 }; + c(); + `, + expected: 3, + }, + } + + runVmTests(t, tests) +} + +func TestFunctionsWithReturnStatement(t *testing.T) { + tests := []vmTestCase{ + { + input: ` + let earlyExit = fn() { return 99; 100; }; + earlyExit(); + `, + expected: 99, + }, + { + input: ` + let earlyExit = fn() { return 99; return 100; }; + earlyExit(); + `, + expected: 99, + }, + } + + runVmTests(t, tests) +} + +func TestFunctionsWithoutReturnValue(t *testing.T) { + tests := []vmTestCase{ + { + input: ` + let noReturn = fn() { }; + noReturn(); + `, + expected: Null, + }, + { + input: ` + let noReturn = fn() { }; + let noReturnTwo = fn() { noReturn(); }; + noReturn(); + noReturnTwo(); + `, + expected: Null, + }, + } + + runVmTests(t, tests) +} + +func TestFirstClassFunctions(t *testing.T) { + tests := []vmTestCase{ + { + input: ` + let returnsOne = fn() { 1; }; + let returnsOneReturner = fn() { returnsOne; }; + returnsOneReturner()(); + `, + expected: 1, + }, + { + input: ` + let returnsOneReturner = fn() { + let returnsOne = fn() { 1; }; + returnsOne; + }; + returnsOneReturner()(); + `, + expected: 1, + }, + } + + runVmTests(t, tests) +} + +func TestCallingFunctionsWithBindings(t *testing.T) { + tests := []vmTestCase{ + { + input: ` + let one = fn() { let one = 1; one }; + one(); + `, + expected: 1, + }, + { + input: ` + let oneAndTwo = fn() { let one = 1; let two = 2; one + two; }; + oneAndTwo(); + `, + expected: 3, + }, + { + input: ` + let oneAndTwo = fn() { let one = 1; let two = 2; one + two; }; + let threeAndFour = fn() { let three = 3; let four = 4; three + four; }; + oneAndTwo() + threeAndFour(); + `, + expected: 10, + }, + { + input: ` + let firstFoobar = fn() { let foobar = 50; foobar; }; + let secondFoobar = fn() { let foobar = 100; foobar; }; + firstFoobar() + secondFoobar(); + `, + expected: 150, + }, + { + input: ` + let globalSeed = 50; + let minusOne = fn() { + let num = 1; + globalSeed - num; + } + let minusTwo = fn() { + let num = 2; + globalSeed - num; + } + minusOne() + minusTwo(); + `, + expected: 97, + }, + } + + runVmTests(t, tests) +} + +func TestCallingFunctionsWithArgumentsAndBindings(t *testing.T) { + tests := []vmTestCase{ + { + input: ` + let identity = fn(a) { a; }; + identity(4); + `, + expected: 4, + }, + { + input: ` + let sum = fn(a, b) { a + b; }; + sum(1, 2); + `, + expected: 3, + }, + { + input: ` + let sum = fn(a, b) { + let c = a + b; + c; + }; + sum(1, 2); + `, + expected: 3, + }, + { + input: ` + let sum = fn(a, b) { + let c = a + b; + c; + }; + sum(1, 2) + sum(3, 4);`, + expected: 10, + }, + { + input: ` + let sum = fn(a, b) { + let c = a + b; + c; + }; + let outer = fn() { + sum(1, 2) + sum(3, 4); + }; + outer(); + `, + expected: 10, + }, + { + input: ` + let globalNum = 10; + + let sum = fn(a, b) { + let c = a + b; + c + globalNum; + }; + + let outer = fn() { + sum(1, 2) + sum(3, 4) + globalNum; + }; + + outer() + globalNum; + `, + expected: 50, + }, + } + + runVmTests(t, tests) +} + +func TestCallingFunctionsWithWrongArguments(t *testing.T) { + tests := []vmTestCase{ + { + input: `fn() { 1; }(1);`, + expected: `wrong number of arguments: want=0, got=1`, + }, + { + input: `fn(a) { a; }();`, + expected: `wrong number of arguments: want=1, got=0`, + }, + { + input: `fn(a, b) { a + b; }(1);`, + expected: `wrong number of arguments: want=2, got=1`, + }, + } + + for _, tt := range tests { + program := parse(tt.input) + + comp := compiler.New() + err := comp.Compile(program) + if err != nil { + t.Fatalf("compiler error: %s", err) + } + + vm := New(comp.Bytecode()) + err = vm.Run() + if err == nil { + t.Fatalf("expected VM error but resulted in none.") + } + + if err.Error() != tt.expected { + t.Fatalf("wrong VM error: want=%q, got=%q", tt.expected, err) + } + } +} + +type vmTestCase struct { + input string + expected interface{} +} + +func runVmTests(t *testing.T, tests []vmTestCase) { + t.Helper() + + for _, tt := range tests { + program := parse(tt.input) + + comp := compiler.New() + err := comp.Compile(program) + if err != nil { + t.Fatalf("compiler error: %s", err) + } + + vm := New(comp.Bytecode()) + err = vm.Run() + if err != nil { + t.Fatalf("vm error: %s", err) + } + + stackElem := vm.LastPoppedStackElem() + + testExpectedObject(t, tt.expected, stackElem) + } +} + +func parse(input string) *ast.Program { + l := lexer.New(input) + p := parser.New(l) + return p.ParseProgram() +} + +func testExpectedObject( + t *testing.T, + expected interface{}, + actual object.Object, +) { + t.Helper() + + switch expected := expected.(type) { + case int: + err := testIntegerObject(int64(expected), actual) + if err != nil { + t.Errorf("testIntegerObject failed: %s", err) + } + + case bool: + err := testBooleanObject(bool(expected), actual) + if err != nil { + t.Errorf("testBooleanObject failed: %s", err) + } + + case *object.Null: + if actual != Null { + t.Errorf("object is not Null: %T (%+v)", actual, actual) + } + + case string: + err := testStringObject(expected, actual) + if err != nil { + t.Errorf("testStringObject failed: %s", err) + } + + case []int: + array, ok := actual.(*object.Array) + if !ok { + t.Errorf("object not Array: %T (%+v)", actual, actual) + return + } + + if len(array.Elements) != len(expected) { + t.Errorf("wrong num of elements. want=%d, got=%d", + len(expected), len(array.Elements)) + return + } + + for i, expectedElem := range expected { + err := testIntegerObject(int64(expectedElem), array.Elements[i]) + if err != nil { + t.Errorf("testIntegerObject failed: %s", err) + } + } + + case map[object.HashKey]int64: + hash, ok := actual.(*object.Hash) + if !ok { + t.Errorf("object is not Hash. got=%T (%+v)", actual, actual) + return + } + + if len(hash.Pairs) != len(expected) { + t.Errorf("hash has wrong number of Pairs. want=%d, got=%d", + len(expected), len(hash.Pairs)) + return + } + + for expectedKey, expectedValue := range expected { + pair, ok := hash.Pairs[expectedKey] + if !ok { + t.Errorf("no pair for given key in Pairs") + } + + err := testIntegerObject(expectedValue, pair.Value) + if err != nil { + t.Errorf("testIntegerObject failed: %s", err) + } + } + } +} + +func testIntegerObject(expected int64, actual object.Object) error { + result, ok := actual.(*object.Integer) + if !ok { + return fmt.Errorf("object is not Integer. got=%T (%+v)", + actual, actual) + } + + if result.Value != expected { + return fmt.Errorf("object has wrong value. got=%d, want=%d", + result.Value, expected) + } + + return nil +} + +func testBooleanObject(expected bool, actual object.Object) error { + result, ok := actual.(*object.Boolean) + if !ok { + return fmt.Errorf("object is not Boolean. got=%T (%+v)", + actual, actual) + } + + if result.Value != expected { + return fmt.Errorf("object has wrong value. got=%t, want=%t", + result.Value, expected) + } + + return nil +} + +func testStringObject(expected string, actual object.Object) error { + result, ok := actual.(*object.String) + if !ok { + return fmt.Errorf("object is not String. got=%T (%+v)", + actual, actual) + } + + if result.Value != expected { + return fmt.Errorf("object has wrong value. got=%q, want=%q", + result.Value, expected) + } + + return nil +} diff --git a/wcig_code_1_2/08/.envrc b/wcig_code_1_2/08/.envrc new file mode 100644 index 0000000..fb65ef4 --- /dev/null +++ b/wcig_code_1_2/08/.envrc @@ -0,0 +1 @@ +export GOPATH=$(pwd) diff --git a/wcig_code_1_2/08/src/monkey/ast/ast.go b/wcig_code_1_2/08/src/monkey/ast/ast.go new file mode 100644 index 0000000..fb30b05 --- /dev/null +++ b/wcig_code_1_2/08/src/monkey/ast/ast.go @@ -0,0 +1,339 @@ +package ast + +import ( + "bytes" + "monkey/token" + "strings" +) + +// The base Node interface +type Node interface { + TokenLiteral() string + String() string +} + +// All statement nodes implement this +type Statement interface { + Node + statementNode() +} + +// All expression nodes implement this +type Expression interface { + Node + expressionNode() +} + +type Program struct { + Statements []Statement +} + +func (p *Program) TokenLiteral() string { + if len(p.Statements) > 0 { + return p.Statements[0].TokenLiteral() + } else { + return "" + } +} + +func (p *Program) String() string { + var out bytes.Buffer + + for _, s := range p.Statements { + out.WriteString(s.String()) + } + + return out.String() +} + +// Statements +type LetStatement struct { + Token token.Token // the token.LET token + Name *Identifier + Value Expression +} + +func (ls *LetStatement) statementNode() {} +func (ls *LetStatement) TokenLiteral() string { return ls.Token.Literal } +func (ls *LetStatement) String() string { + var out bytes.Buffer + + out.WriteString(ls.TokenLiteral() + " ") + out.WriteString(ls.Name.String()) + out.WriteString(" = ") + + if ls.Value != nil { + out.WriteString(ls.Value.String()) + } + + out.WriteString(";") + + return out.String() +} + +type ReturnStatement struct { + Token token.Token // the 'return' token + ReturnValue Expression +} + +func (rs *ReturnStatement) statementNode() {} +func (rs *ReturnStatement) TokenLiteral() string { return rs.Token.Literal } +func (rs *ReturnStatement) String() string { + var out bytes.Buffer + + out.WriteString(rs.TokenLiteral() + " ") + + if rs.ReturnValue != nil { + out.WriteString(rs.ReturnValue.String()) + } + + out.WriteString(";") + + return out.String() +} + +type ExpressionStatement struct { + Token token.Token // the first token of the expression + Expression Expression +} + +func (es *ExpressionStatement) statementNode() {} +func (es *ExpressionStatement) TokenLiteral() string { return es.Token.Literal } +func (es *ExpressionStatement) String() string { + if es.Expression != nil { + return es.Expression.String() + } + return "" +} + +type BlockStatement struct { + Token token.Token // the { token + Statements []Statement +} + +func (bs *BlockStatement) statementNode() {} +func (bs *BlockStatement) TokenLiteral() string { return bs.Token.Literal } +func (bs *BlockStatement) String() string { + var out bytes.Buffer + + for _, s := range bs.Statements { + out.WriteString(s.String()) + } + + return out.String() +} + +// Expressions +type Identifier struct { + Token token.Token // the token.IDENT token + Value string +} + +func (i *Identifier) expressionNode() {} +func (i *Identifier) TokenLiteral() string { return i.Token.Literal } +func (i *Identifier) String() string { return i.Value } + +type Boolean struct { + Token token.Token + Value bool +} + +func (b *Boolean) expressionNode() {} +func (b *Boolean) TokenLiteral() string { return b.Token.Literal } +func (b *Boolean) String() string { return b.Token.Literal } + +type IntegerLiteral struct { + Token token.Token + Value int64 +} + +func (il *IntegerLiteral) expressionNode() {} +func (il *IntegerLiteral) TokenLiteral() string { return il.Token.Literal } +func (il *IntegerLiteral) String() string { return il.Token.Literal } + +type PrefixExpression struct { + Token token.Token // The prefix token, e.g. ! + Operator string + Right Expression +} + +func (pe *PrefixExpression) expressionNode() {} +func (pe *PrefixExpression) TokenLiteral() string { return pe.Token.Literal } +func (pe *PrefixExpression) String() string { + var out bytes.Buffer + + out.WriteString("(") + out.WriteString(pe.Operator) + out.WriteString(pe.Right.String()) + out.WriteString(")") + + return out.String() +} + +type InfixExpression struct { + Token token.Token // The operator token, e.g. + + Left Expression + Operator string + Right Expression +} + +func (oe *InfixExpression) expressionNode() {} +func (oe *InfixExpression) TokenLiteral() string { return oe.Token.Literal } +func (oe *InfixExpression) String() string { + var out bytes.Buffer + + out.WriteString("(") + out.WriteString(oe.Left.String()) + out.WriteString(" " + oe.Operator + " ") + out.WriteString(oe.Right.String()) + out.WriteString(")") + + return out.String() +} + +type IfExpression struct { + Token token.Token // The 'if' token + Condition Expression + Consequence *BlockStatement + Alternative *BlockStatement +} + +func (ie *IfExpression) expressionNode() {} +func (ie *IfExpression) TokenLiteral() string { return ie.Token.Literal } +func (ie *IfExpression) String() string { + var out bytes.Buffer + + out.WriteString("if") + out.WriteString(ie.Condition.String()) + out.WriteString(" ") + out.WriteString(ie.Consequence.String()) + + if ie.Alternative != nil { + out.WriteString("else ") + out.WriteString(ie.Alternative.String()) + } + + return out.String() +} + +type FunctionLiteral struct { + Token token.Token // The 'fn' token + Parameters []*Identifier + Body *BlockStatement +} + +func (fl *FunctionLiteral) expressionNode() {} +func (fl *FunctionLiteral) TokenLiteral() string { return fl.Token.Literal } +func (fl *FunctionLiteral) String() string { + var out bytes.Buffer + + params := []string{} + for _, p := range fl.Parameters { + params = append(params, p.String()) + } + + out.WriteString(fl.TokenLiteral()) + out.WriteString("(") + out.WriteString(strings.Join(params, ", ")) + out.WriteString(") ") + out.WriteString(fl.Body.String()) + + return out.String() +} + +type CallExpression struct { + Token token.Token // The '(' token + Function Expression // Identifier or FunctionLiteral + Arguments []Expression +} + +func (ce *CallExpression) expressionNode() {} +func (ce *CallExpression) TokenLiteral() string { return ce.Token.Literal } +func (ce *CallExpression) String() string { + var out bytes.Buffer + + args := []string{} + for _, a := range ce.Arguments { + args = append(args, a.String()) + } + + out.WriteString(ce.Function.String()) + out.WriteString("(") + out.WriteString(strings.Join(args, ", ")) + out.WriteString(")") + + return out.String() +} + +type StringLiteral struct { + Token token.Token + Value string +} + +func (sl *StringLiteral) expressionNode() {} +func (sl *StringLiteral) TokenLiteral() string { return sl.Token.Literal } +func (sl *StringLiteral) String() string { return sl.Token.Literal } + +type ArrayLiteral struct { + Token token.Token // the '[' token + Elements []Expression +} + +func (al *ArrayLiteral) expressionNode() {} +func (al *ArrayLiteral) TokenLiteral() string { return al.Token.Literal } +func (al *ArrayLiteral) String() string { + var out bytes.Buffer + + elements := []string{} + for _, el := range al.Elements { + elements = append(elements, el.String()) + } + + out.WriteString("[") + out.WriteString(strings.Join(elements, ", ")) + out.WriteString("]") + + return out.String() +} + +type IndexExpression struct { + Token token.Token // The [ token + Left Expression + Index Expression +} + +func (ie *IndexExpression) expressionNode() {} +func (ie *IndexExpression) TokenLiteral() string { return ie.Token.Literal } +func (ie *IndexExpression) String() string { + var out bytes.Buffer + + out.WriteString("(") + out.WriteString(ie.Left.String()) + out.WriteString("[") + out.WriteString(ie.Index.String()) + out.WriteString("])") + + return out.String() +} + +type HashLiteral struct { + Token token.Token // the '{' token + Pairs map[Expression]Expression +} + +func (hl *HashLiteral) expressionNode() {} +func (hl *HashLiteral) TokenLiteral() string { return hl.Token.Literal } +func (hl *HashLiteral) String() string { + var out bytes.Buffer + + pairs := []string{} + for key, value := range hl.Pairs { + pairs = append(pairs, key.String()+":"+value.String()) + } + + out.WriteString("{") + out.WriteString(strings.Join(pairs, ", ")) + out.WriteString("}") + + return out.String() +} diff --git a/wcig_code_1_2/08/src/monkey/ast/ast_test.go b/wcig_code_1_2/08/src/monkey/ast/ast_test.go new file mode 100644 index 0000000..14a49dc --- /dev/null +++ b/wcig_code_1_2/08/src/monkey/ast/ast_test.go @@ -0,0 +1,28 @@ +package ast + +import ( + "monkey/token" + "testing" +) + +func TestString(t *testing.T) { + program := &Program{ + Statements: []Statement{ + &LetStatement{ + Token: token.Token{Type: token.LET, Literal: "let"}, + Name: &Identifier{ + Token: token.Token{Type: token.IDENT, Literal: "myVar"}, + Value: "myVar", + }, + Value: &Identifier{ + Token: token.Token{Type: token.IDENT, Literal: "anotherVar"}, + Value: "anotherVar", + }, + }, + }, + } + + if program.String() != "let myVar = anotherVar;" { + t.Errorf("program.String() wrong. got=%q", program.String()) + } +} diff --git a/wcig_code_1_2/08/src/monkey/code/code.go b/wcig_code_1_2/08/src/monkey/code/code.go new file mode 100644 index 0000000..08311e8 --- /dev/null +++ b/wcig_code_1_2/08/src/monkey/code/code.go @@ -0,0 +1,205 @@ +package code + +import ( + "bytes" + "encoding/binary" + "fmt" +) + +type Instructions []byte + +func (ins Instructions) String() string { + var out bytes.Buffer + + i := 0 + for i < len(ins) { + def, err := Lookup(ins[i]) + if err != nil { + fmt.Fprintf(&out, "ERROR: %s\n", err) + continue + } + + operands, read := ReadOperands(def, ins[i+1:]) + + fmt.Fprintf(&out, "%04d %s\n", i, ins.fmtInstruction(def, operands)) + + i += 1 + read + } + + return out.String() +} + +func (ins Instructions) fmtInstruction(def *Definition, operands []int) string { + operandCount := len(def.OperandWidths) + + if len(operands) != operandCount { + return fmt.Sprintf("ERROR: operand len %d does not match defined %d\n", + len(operands), operandCount) + } + + switch operandCount { + case 0: + return def.Name + case 1: + return fmt.Sprintf("%s %d", def.Name, operands[0]) + } + + return fmt.Sprintf("ERROR: unhandled operandCount for %s\n", def.Name) +} + +type Opcode byte + +const ( + OpConstant Opcode = iota + + OpAdd + + OpPop + + OpSub + OpMul + OpDiv + + OpTrue + OpFalse + + OpEqual + OpNotEqual + OpGreaterThan + + OpMinus + OpBang + + OpJumpNotTruthy + OpJump + + OpNull + + OpGetGlobal + OpSetGlobal + + OpArray + OpHash + OpIndex + + OpCall + + OpReturnValue + OpReturn + + OpGetLocal + OpSetLocal + + OpGetBuiltin +) + +type Definition struct { + Name string + OperandWidths []int +} + +var definitions = map[Opcode]*Definition{ + OpConstant: {"OpConstant", []int{2}}, + + OpAdd: {"OpAdd", []int{}}, + + OpPop: {"OpPop", []int{}}, + + OpSub: {"OpSub", []int{}}, + OpMul: {"OpMul", []int{}}, + OpDiv: {"OpDiv", []int{}}, + + OpTrue: {"OpTrue", []int{}}, + OpFalse: {"OpFalse", []int{}}, + + OpEqual: {"OpEqual", []int{}}, + OpNotEqual: {"OpNotEqual", []int{}}, + OpGreaterThan: {"OpGreaterThan", []int{}}, + + OpMinus: {"OpMinus", []int{}}, + OpBang: {"OpBang", []int{}}, + + OpJumpNotTruthy: {"OpJumpNotTruthy", []int{2}}, + OpJump: {"OpJump", []int{2}}, + + OpNull: {"OpNull", []int{}}, + + OpGetGlobal: {"OpGetGlobal", []int{2}}, + OpSetGlobal: {"OpSetGlobal", []int{2}}, + + OpArray: {"OpArray", []int{2}}, + OpHash: {"OpHash", []int{2}}, + OpIndex: {"OpIndex", []int{}}, + + OpCall: {"OpCall", []int{1}}, + + OpReturnValue: {"OpReturnValue", []int{}}, + OpReturn: {"OpReturn", []int{}}, + + OpGetLocal: {"OpGetLocal", []int{1}}, + OpSetLocal: {"OpSetLocal", []int{1}}, + + OpGetBuiltin: {"OpGetBuiltin", []int{1}}, +} + +func Lookup(op byte) (*Definition, error) { + def, ok := definitions[Opcode(op)] + if !ok { + return nil, fmt.Errorf("opcode %d undefined", op) + } + + return def, nil +} + +func Make(op Opcode, operands ...int) []byte { + def, ok := definitions[op] + if !ok { + return []byte{} + } + + instructionLen := 1 + for _, w := range def.OperandWidths { + instructionLen += w + } + + instruction := make([]byte, instructionLen) + instruction[0] = byte(op) + + offset := 1 + for i, o := range operands { + width := def.OperandWidths[i] + switch width { + case 2: + binary.BigEndian.PutUint16(instruction[offset:], uint16(o)) + case 1: + instruction[offset] = byte(o) + } + offset += width + } + + return instruction +} + +func ReadOperands(def *Definition, ins Instructions) ([]int, int) { + operands := make([]int, len(def.OperandWidths)) + offset := 0 + + for i, width := range def.OperandWidths { + switch width { + case 2: + operands[i] = int(ReadUint16(ins[offset:])) + case 1: + operands[i] = int(ReadUint8(ins[offset:])) + } + + offset += width + } + + return operands, offset +} + +func ReadUint8(ins Instructions) uint8 { return uint8(ins[0]) } + +func ReadUint16(ins Instructions) uint16 { + return binary.BigEndian.Uint16(ins) +} diff --git a/wcig_code_1_2/08/src/monkey/code/code_test.go b/wcig_code_1_2/08/src/monkey/code/code_test.go new file mode 100644 index 0000000..71f8f73 --- /dev/null +++ b/wcig_code_1_2/08/src/monkey/code/code_test.go @@ -0,0 +1,87 @@ +package code + +import "testing" + +func TestMake(t *testing.T) { + tests := []struct { + op Opcode + operands []int + expected []byte + }{ + {OpConstant, []int{65534}, []byte{byte(OpConstant), 255, 254}}, + {OpAdd, []int{}, []byte{byte(OpAdd)}}, + {OpGetLocal, []int{255}, []byte{byte(OpGetLocal), 255}}, + } + + for _, tt := range tests { + instruction := Make(tt.op, tt.operands...) + + if len(instruction) != len(tt.expected) { + t.Errorf("instruction has wrong length. want=%d, got=%d", + len(tt.expected), len(instruction)) + } + + for i, b := range tt.expected { + if instruction[i] != tt.expected[i] { + t.Errorf("wrong byte at pos %d. want=%d, got=%d", + i, b, instruction[i]) + } + } + } +} + +func TestInstructionsString(t *testing.T) { + instructions := []Instructions{ + Make(OpAdd), + Make(OpGetLocal, 1), + Make(OpConstant, 2), + Make(OpConstant, 65535), + } + + expected := `0000 OpAdd +0001 OpGetLocal 1 +0003 OpConstant 2 +0006 OpConstant 65535 +` + + concatted := Instructions{} + for _, ins := range instructions { + concatted = append(concatted, ins...) + } + + if concatted.String() != expected { + t.Errorf("instructions wrongly formatted.\nwant=%q\ngot=%q", + expected, concatted.String()) + } +} + +func TestReadOperands(t *testing.T) { + tests := []struct { + op Opcode + operands []int + bytesRead int + }{ + {OpConstant, []int{65535}, 2}, + {OpGetLocal, []int{255}, 1}, + } + + for _, tt := range tests { + instruction := Make(tt.op, tt.operands...) + + def, err := Lookup(byte(tt.op)) + if err != nil { + t.Fatalf("definition not found: %q\n", err) + } + + operandsRead, n := ReadOperands(def, instruction[1:]) + if n != tt.bytesRead { + t.Fatalf("n wrong. want=%d, got=%d", tt.bytesRead, n) + } + + for i, want := range tt.operands { + if operandsRead[i] != want { + t.Errorf("operand wrong. want=%d, got=%d", want, operandsRead[i]) + } + } + } +} diff --git a/wcig_code_1_2/08/src/monkey/compiler/compiler.go b/wcig_code_1_2/08/src/monkey/compiler/compiler.go new file mode 100644 index 0000000..993eb57 --- /dev/null +++ b/wcig_code_1_2/08/src/monkey/compiler/compiler.go @@ -0,0 +1,441 @@ +package compiler + +import ( + "fmt" + "monkey/ast" + "monkey/code" + "monkey/object" + "sort" +) + +type Compiler struct { + constants []object.Object + + symbolTable *SymbolTable + + scopes []CompilationScope + scopeIndex int +} + +func New() *Compiler { + mainScope := CompilationScope{ + instructions: code.Instructions{}, + lastInstruction: EmittedInstruction{}, + previousInstruction: EmittedInstruction{}, + } + + symbolTable := NewSymbolTable() + + for i, v := range object.Builtins { + symbolTable.DefineBuiltin(i, v.Name) + } + + return &Compiler{ + constants: []object.Object{}, + symbolTable: symbolTable, + scopes: []CompilationScope{mainScope}, + scopeIndex: 0, + } +} + +func NewWithState(s *SymbolTable, constants []object.Object) *Compiler { + compiler := New() + compiler.symbolTable = s + compiler.constants = constants + return compiler +} + +func (c *Compiler) Compile(node ast.Node) error { + switch node := node.(type) { + case *ast.Program: + for _, s := range node.Statements { + err := c.Compile(s) + if err != nil { + return err + } + } + + case *ast.ExpressionStatement: + err := c.Compile(node.Expression) + if err != nil { + return err + } + c.emit(code.OpPop) + + case *ast.InfixExpression: + if node.Operator == "<" { + err := c.Compile(node.Right) + if err != nil { + return err + } + + err = c.Compile(node.Left) + if err != nil { + return err + } + c.emit(code.OpGreaterThan) + return nil + } + + err := c.Compile(node.Left) + if err != nil { + return err + } + + err = c.Compile(node.Right) + if err != nil { + return err + } + + switch node.Operator { + case "+": + c.emit(code.OpAdd) + case "-": + c.emit(code.OpSub) + case "*": + c.emit(code.OpMul) + case "/": + c.emit(code.OpDiv) + case ">": + c.emit(code.OpGreaterThan) + case "==": + c.emit(code.OpEqual) + case "!=": + c.emit(code.OpNotEqual) + default: + return fmt.Errorf("unknown operator %s", node.Operator) + } + + case *ast.IntegerLiteral: + integer := &object.Integer{Value: node.Value} + c.emit(code.OpConstant, c.addConstant(integer)) + + case *ast.Boolean: + if node.Value { + c.emit(code.OpTrue) + } else { + c.emit(code.OpFalse) + } + + case *ast.PrefixExpression: + err := c.Compile(node.Right) + if err != nil { + return err + } + + switch node.Operator { + case "!": + c.emit(code.OpBang) + case "-": + c.emit(code.OpMinus) + default: + return fmt.Errorf("unknown operator %s", node.Operator) + } + + case *ast.IfExpression: + err := c.Compile(node.Condition) + if err != nil { + return err + } + + // Emit an `OpJumpNotTruthy` with a bogus value + jumpNotTruthyPos := c.emit(code.OpJumpNotTruthy, 9999) + + err = c.Compile(node.Consequence) + if err != nil { + return err + } + + if c.lastInstructionIs(code.OpPop) { + c.removeLastPop() + } + + // Emit an `OpJump` with a bogus value + jumpPos := c.emit(code.OpJump, 9999) + + afterConsequencePos := len(c.currentInstructions()) + c.changeOperand(jumpNotTruthyPos, afterConsequencePos) + + if node.Alternative == nil { + c.emit(code.OpNull) + } else { + err := c.Compile(node.Alternative) + if err != nil { + return err + } + + if c.lastInstructionIs(code.OpPop) { + c.removeLastPop() + } + } + + afterAlternativePos := len(c.currentInstructions()) + c.changeOperand(jumpPos, afterAlternativePos) + + case *ast.BlockStatement: + for _, s := range node.Statements { + err := c.Compile(s) + if err != nil { + return err + } + } + + case *ast.LetStatement: + err := c.Compile(node.Value) + if err != nil { + return err + } + + symbol := c.symbolTable.Define(node.Name.Value) + if symbol.Scope == GlobalScope { + c.emit(code.OpSetGlobal, symbol.Index) + } else { + c.emit(code.OpSetLocal, symbol.Index) + } + + case *ast.Identifier: + symbol, ok := c.symbolTable.Resolve(node.Value) + if !ok { + return fmt.Errorf("undefined variable %s", node.Value) + } + + c.loadSymbol(symbol) + + case *ast.StringLiteral: + str := &object.String{Value: node.Value} + c.emit(code.OpConstant, c.addConstant(str)) + + case *ast.ArrayLiteral: + for _, el := range node.Elements { + err := c.Compile(el) + if err != nil { + return err + } + } + + c.emit(code.OpArray, len(node.Elements)) + + case *ast.HashLiteral: + keys := []ast.Expression{} + for k := range node.Pairs { + keys = append(keys, k) + } + sort.Slice(keys, func(i, j int) bool { + return keys[i].String() < keys[j].String() + }) + + for _, k := range keys { + err := c.Compile(k) + if err != nil { + return err + } + err = c.Compile(node.Pairs[k]) + if err != nil { + return err + } + } + + c.emit(code.OpHash, len(node.Pairs)*2) + + case *ast.IndexExpression: + err := c.Compile(node.Left) + if err != nil { + return err + } + + err = c.Compile(node.Index) + if err != nil { + return err + } + + c.emit(code.OpIndex) + + case *ast.FunctionLiteral: + c.enterScope() + + for _, p := range node.Parameters { + c.symbolTable.Define(p.Value) + } + + err := c.Compile(node.Body) + if err != nil { + return err + } + + if c.lastInstructionIs(code.OpPop) { + c.replaceLastPopWithReturn() + } + if !c.lastInstructionIs(code.OpReturnValue) { + c.emit(code.OpReturn) + } + + numLocals := c.symbolTable.numDefinitions + instructions := c.leaveScope() + + compiledFn := &object.CompiledFunction{ + Instructions: instructions, + NumLocals: numLocals, + NumParameters: len(node.Parameters), + } + c.emit(code.OpConstant, c.addConstant(compiledFn)) + + case *ast.ReturnStatement: + err := c.Compile(node.ReturnValue) + if err != nil { + return err + } + + c.emit(code.OpReturnValue) + + case *ast.CallExpression: + err := c.Compile(node.Function) + if err != nil { + return err + } + + for _, a := range node.Arguments { + err := c.Compile(a) + if err != nil { + return err + } + } + + c.emit(code.OpCall, len(node.Arguments)) + + } + + return nil +} + +func (c *Compiler) Bytecode() *Bytecode { + return &Bytecode{ + Instructions: c.currentInstructions(), + Constants: c.constants, + } +} + +func (c *Compiler) addConstant(obj object.Object) int { + c.constants = append(c.constants, obj) + return len(c.constants) - 1 +} + +func (c *Compiler) emit(op code.Opcode, operands ...int) int { + ins := code.Make(op, operands...) + pos := c.addInstruction(ins) + + c.setLastInstruction(op, pos) + + return pos +} + +func (c *Compiler) addInstruction(ins []byte) int { + posNewInstruction := len(c.currentInstructions()) + updatedInstructions := append(c.currentInstructions(), ins...) + + c.scopes[c.scopeIndex].instructions = updatedInstructions + + return posNewInstruction +} + +func (c *Compiler) setLastInstruction(op code.Opcode, pos int) { + previous := c.scopes[c.scopeIndex].lastInstruction + last := EmittedInstruction{Opcode: op, Position: pos} + + c.scopes[c.scopeIndex].previousInstruction = previous + c.scopes[c.scopeIndex].lastInstruction = last +} + +func (c *Compiler) lastInstructionIs(op code.Opcode) bool { + if len(c.currentInstructions()) == 0 { + return false + } + + return c.scopes[c.scopeIndex].lastInstruction.Opcode == op +} + +func (c *Compiler) removeLastPop() { + last := c.scopes[c.scopeIndex].lastInstruction + previous := c.scopes[c.scopeIndex].previousInstruction + + old := c.currentInstructions() + new := old[:last.Position] + + c.scopes[c.scopeIndex].instructions = new + c.scopes[c.scopeIndex].lastInstruction = previous +} + +func (c *Compiler) replaceInstruction(pos int, newInstruction []byte) { + ins := c.currentInstructions() + + for i := 0; i < len(newInstruction); i++ { + ins[pos+i] = newInstruction[i] + } +} + +func (c *Compiler) changeOperand(opPos int, operand int) { + op := code.Opcode(c.currentInstructions()[opPos]) + newInstruction := code.Make(op, operand) + + c.replaceInstruction(opPos, newInstruction) +} + +func (c *Compiler) currentInstructions() code.Instructions { + return c.scopes[c.scopeIndex].instructions +} + +func (c *Compiler) enterScope() { + scope := CompilationScope{ + instructions: code.Instructions{}, + lastInstruction: EmittedInstruction{}, + previousInstruction: EmittedInstruction{}, + } + c.scopes = append(c.scopes, scope) + c.scopeIndex++ + + c.symbolTable = NewEnclosedSymbolTable(c.symbolTable) +} + +func (c *Compiler) leaveScope() code.Instructions { + instructions := c.currentInstructions() + + c.scopes = c.scopes[:len(c.scopes)-1] + c.scopeIndex-- + + c.symbolTable = c.symbolTable.Outer + + return instructions +} + +func (c *Compiler) replaceLastPopWithReturn() { + lastPos := c.scopes[c.scopeIndex].lastInstruction.Position + c.replaceInstruction(lastPos, code.Make(code.OpReturnValue)) + + c.scopes[c.scopeIndex].lastInstruction.Opcode = code.OpReturnValue +} + +func (c *Compiler) loadSymbol(s Symbol) { + switch s.Scope { + case GlobalScope: + c.emit(code.OpGetGlobal, s.Index) + case LocalScope: + c.emit(code.OpGetLocal, s.Index) + case BuiltinScope: + c.emit(code.OpGetBuiltin, s.Index) + } +} + +type Bytecode struct { + Instructions code.Instructions + Constants []object.Object +} + +type EmittedInstruction struct { + Opcode code.Opcode + Position int +} + +type CompilationScope struct { + instructions code.Instructions + lastInstruction EmittedInstruction + previousInstruction EmittedInstruction +} diff --git a/wcig_code_1_2/08/src/monkey/compiler/compiler_test.go b/wcig_code_1_2/08/src/monkey/compiler/compiler_test.go new file mode 100644 index 0000000..e8988b3 --- /dev/null +++ b/wcig_code_1_2/08/src/monkey/compiler/compiler_test.go @@ -0,0 +1,910 @@ +package compiler + +import ( + "fmt" + "monkey/ast" + "monkey/code" + "monkey/lexer" + "monkey/object" + "monkey/parser" + "testing" +) + +func TestCompilerScopes(t *testing.T) { + compiler := New() + if compiler.scopeIndex != 0 { + t.Errorf("scopeIndex wrong. got=%d, want=%d", compiler.scopeIndex, 0) + } + globalSymbolTable := compiler.symbolTable + + compiler.emit(code.OpMul) + + compiler.enterScope() + if compiler.scopeIndex != 1 { + t.Errorf("scopeIndex wrong. got=%d, want=%d", compiler.scopeIndex, 1) + } + + compiler.emit(code.OpSub) + + if len(compiler.scopes[compiler.scopeIndex].instructions) != 1 { + t.Errorf("instructions length wrong. got=%d", + len(compiler.scopes[compiler.scopeIndex].instructions)) + } + + last := compiler.scopes[compiler.scopeIndex].lastInstruction + if last.Opcode != code.OpSub { + t.Errorf("lastInstruction.Opcode wrong. got=%d, want=%d", + last.Opcode, code.OpSub) + } + + if compiler.symbolTable.Outer != globalSymbolTable { + t.Errorf("compiler did not enclose symbolTable") + } + + compiler.leaveScope() + if compiler.scopeIndex != 0 { + t.Errorf("scopeIndex wrong. got=%d, want=%d", + compiler.scopeIndex, 0) + } + + if compiler.symbolTable != globalSymbolTable { + t.Errorf("compiler did not restore global symbol table") + } + if compiler.symbolTable.Outer != nil { + t.Errorf("compiler modified global symbol table incorrectly") + } + + compiler.emit(code.OpAdd) + + if len(compiler.scopes[compiler.scopeIndex].instructions) != 2 { + t.Errorf("instructions length wrong. got=%d", + len(compiler.scopes[compiler.scopeIndex].instructions)) + } + + last = compiler.scopes[compiler.scopeIndex].lastInstruction + if last.Opcode != code.OpAdd { + t.Errorf("lastInstruction.Opcode wrong. got=%d, want=%d", + last.Opcode, code.OpAdd) + } + + previous := compiler.scopes[compiler.scopeIndex].previousInstruction + if previous.Opcode != code.OpMul { + t.Errorf("previousInstruction.Opcode wrong. got=%d, want=%d", + previous.Opcode, code.OpMul) + } +} + +func TestIntegerArithmetic(t *testing.T) { + tests := []compilerTestCase{ + { + input: "1 + 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpAdd), + code.Make(code.OpPop), + }, + }, + { + input: "1; 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpPop), + code.Make(code.OpConstant, 1), + code.Make(code.OpPop), + }, + }, + { + input: "1 - 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpSub), + code.Make(code.OpPop), + }, + }, + { + input: "1 * 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpMul), + code.Make(code.OpPop), + }, + }, + { + input: "2 / 1", + expectedConstants: []interface{}{2, 1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpDiv), + code.Make(code.OpPop), + }, + }, + { + input: "-1", + expectedConstants: []interface{}{1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpMinus), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestBooleanExpressions(t *testing.T) { + tests := []compilerTestCase{ + { + input: "true", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpTrue), + code.Make(code.OpPop), + }, + }, + { + input: "false", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpFalse), + code.Make(code.OpPop), + }, + }, + { + input: "1 > 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpGreaterThan), + code.Make(code.OpPop), + }, + }, + { + input: "1 < 2", + expectedConstants: []interface{}{2, 1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpGreaterThan), + code.Make(code.OpPop), + }, + }, + { + input: "1 == 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpEqual), + code.Make(code.OpPop), + }, + }, + { + input: "1 != 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpNotEqual), + code.Make(code.OpPop), + }, + }, + { + input: "true == false", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpTrue), + code.Make(code.OpFalse), + code.Make(code.OpEqual), + code.Make(code.OpPop), + }, + }, + { + input: "true != false", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpTrue), + code.Make(code.OpFalse), + code.Make(code.OpNotEqual), + code.Make(code.OpPop), + }, + }, + { + input: "!true", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpTrue), + code.Make(code.OpBang), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestConditionals(t *testing.T) { + tests := []compilerTestCase{ + { + input: ` + if (true) { 10 }; 3333; + `, + expectedConstants: []interface{}{10, 3333}, + expectedInstructions: []code.Instructions{ + // 0000 + code.Make(code.OpTrue), + // 0001 + code.Make(code.OpJumpNotTruthy, 10), + // 0004 + code.Make(code.OpConstant, 0), + // 0007 + code.Make(code.OpJump, 11), + // 0010 + code.Make(code.OpNull), + // 0011 + code.Make(code.OpPop), + // 0012 + code.Make(code.OpConstant, 1), + // 0015 + code.Make(code.OpPop), + }, + }, + { + input: ` + if (true) { 10 } else { 20 }; 3333; + `, + expectedConstants: []interface{}{10, 20, 3333}, + expectedInstructions: []code.Instructions{ + // 0000 + code.Make(code.OpTrue), + // 0001 + code.Make(code.OpJumpNotTruthy, 10), + // 0004 + code.Make(code.OpConstant, 0), + // 0007 + code.Make(code.OpJump, 13), + // 0010 + code.Make(code.OpConstant, 1), + // 0013 + code.Make(code.OpPop), + // 0014 + code.Make(code.OpConstant, 2), + // 0017 + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestGlobalLetStatements(t *testing.T) { + tests := []compilerTestCase{ + { + input: ` + let one = 1; + let two = 2; + `, + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpSetGlobal, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpSetGlobal, 1), + }, + }, + { + input: ` + let one = 1; + one; + `, + expectedConstants: []interface{}{1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpSetGlobal, 0), + code.Make(code.OpGetGlobal, 0), + code.Make(code.OpPop), + }, + }, + { + input: ` + let one = 1; + let two = one; + two; + `, + expectedConstants: []interface{}{1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpSetGlobal, 0), + code.Make(code.OpGetGlobal, 0), + code.Make(code.OpSetGlobal, 1), + code.Make(code.OpGetGlobal, 1), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestFunctions(t *testing.T) { + tests := []compilerTestCase{ + { + input: `fn() { return 5 + 10 }`, + expectedConstants: []interface{}{ + 5, + 10, + []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpAdd), + code.Make(code.OpReturnValue), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 2), + code.Make(code.OpPop), + }, + }, + { + input: `fn() { 5 + 10 }`, + expectedConstants: []interface{}{ + 5, + 10, + []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpAdd), + code.Make(code.OpReturnValue), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 2), + code.Make(code.OpPop), + }, + }, + { + input: `fn() { 1; 2 }`, + expectedConstants: []interface{}{ + 1, + 2, + []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpPop), + code.Make(code.OpConstant, 1), + code.Make(code.OpReturnValue), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 2), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +type compilerTestCase struct { + input string + expectedConstants []interface{} + expectedInstructions []code.Instructions +} + +func runCompilerTests(t *testing.T, tests []compilerTestCase) { + t.Helper() + + for _, tt := range tests { + program := parse(tt.input) + + compiler := New() + err := compiler.Compile(program) + if err != nil { + t.Fatalf("compiler error: %s", err) + } + + bytecode := compiler.Bytecode() + + err = testInstructions(tt.expectedInstructions, bytecode.Instructions) + if err != nil { + t.Fatalf("testInstructions failed: %s", err) + } + + err = testConstants(t, tt.expectedConstants, bytecode.Constants) + if err != nil { + t.Fatalf("testConstants failed: %s", err) + } + } +} + +func TestStringExpressions(t *testing.T) { + tests := []compilerTestCase{ + { + input: `"monkey"`, + expectedConstants: []interface{}{"monkey"}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpPop), + }, + }, + { + input: `"mon" + "key"`, + expectedConstants: []interface{}{"mon", "key"}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpAdd), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestArrayLiterals(t *testing.T) { + tests := []compilerTestCase{ + { + input: "[]", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpArray, 0), + code.Make(code.OpPop), + }, + }, + { + input: "[1, 2, 3]", + expectedConstants: []interface{}{1, 2, 3}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpConstant, 2), + code.Make(code.OpArray, 3), + code.Make(code.OpPop), + }, + }, + { + input: "[1 + 2, 3 - 4, 5 * 6]", + expectedConstants: []interface{}{1, 2, 3, 4, 5, 6}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpAdd), + code.Make(code.OpConstant, 2), + code.Make(code.OpConstant, 3), + code.Make(code.OpSub), + code.Make(code.OpConstant, 4), + code.Make(code.OpConstant, 5), + code.Make(code.OpMul), + code.Make(code.OpArray, 3), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestHashLiterals(t *testing.T) { + tests := []compilerTestCase{ + { + input: "{}", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpHash, 0), + code.Make(code.OpPop), + }, + }, + { + input: "{1: 2, 3: 4, 5: 6}", + expectedConstants: []interface{}{1, 2, 3, 4, 5, 6}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpConstant, 2), + code.Make(code.OpConstant, 3), + code.Make(code.OpConstant, 4), + code.Make(code.OpConstant, 5), + code.Make(code.OpHash, 6), + code.Make(code.OpPop), + }, + }, + { + input: "{1: 2 + 3, 4: 5 * 6}", + expectedConstants: []interface{}{1, 2, 3, 4, 5, 6}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpConstant, 2), + code.Make(code.OpAdd), + code.Make(code.OpConstant, 3), + code.Make(code.OpConstant, 4), + code.Make(code.OpConstant, 5), + code.Make(code.OpMul), + code.Make(code.OpHash, 4), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestIndexExpressions(t *testing.T) { + tests := []compilerTestCase{ + { + input: "[1, 2, 3][1 + 1]", + expectedConstants: []interface{}{1, 2, 3, 1, 1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpConstant, 2), + code.Make(code.OpArray, 3), + code.Make(code.OpConstant, 3), + code.Make(code.OpConstant, 4), + code.Make(code.OpAdd), + code.Make(code.OpIndex), + code.Make(code.OpPop), + }, + }, + { + input: "{1: 2}[2 - 1]", + expectedConstants: []interface{}{1, 2, 2, 1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpHash, 2), + code.Make(code.OpConstant, 2), + code.Make(code.OpConstant, 3), + code.Make(code.OpSub), + code.Make(code.OpIndex), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestFunctionsWithoutReturnValue(t *testing.T) { + tests := []compilerTestCase{ + { + input: `fn() { }`, + expectedConstants: []interface{}{ + []code.Instructions{ + code.Make(code.OpReturn), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestFunctionCalls(t *testing.T) { + tests := []compilerTestCase{ + { + input: `fn() { 24 }();`, + expectedConstants: []interface{}{ + 24, + []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpReturnValue), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 1), + code.Make(code.OpCall, 0), + code.Make(code.OpPop), + }, + }, + { + input: ` + let noArg = fn() { 24 }; + noArg(); + `, + expectedConstants: []interface{}{ + 24, + []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpReturnValue), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 1), + code.Make(code.OpSetGlobal, 0), + code.Make(code.OpGetGlobal, 0), + code.Make(code.OpCall, 0), + code.Make(code.OpPop), + }, + }, + { + input: ` + let oneArg = fn(a) { a }; + oneArg(24); + `, + expectedConstants: []interface{}{ + []code.Instructions{ + code.Make(code.OpGetLocal, 0), + code.Make(code.OpReturnValue), + }, + 24, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpSetGlobal, 0), + code.Make(code.OpGetGlobal, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpCall, 1), + code.Make(code.OpPop), + }, + }, + { + input: ` + let manyArg = fn(a, b, c) { a; b; c }; + manyArg(24, 25, 26); + `, + expectedConstants: []interface{}{ + []code.Instructions{ + code.Make(code.OpGetLocal, 0), + code.Make(code.OpPop), + code.Make(code.OpGetLocal, 1), + code.Make(code.OpPop), + code.Make(code.OpGetLocal, 2), + code.Make(code.OpReturnValue), + }, + 24, + 25, + 26, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpSetGlobal, 0), + code.Make(code.OpGetGlobal, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpConstant, 2), + code.Make(code.OpConstant, 3), + code.Make(code.OpCall, 3), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestLetStatementScopes(t *testing.T) { + tests := []compilerTestCase{ + { + input: ` + let num = 55; + fn() { num } + `, + expectedConstants: []interface{}{ + 55, + []code.Instructions{ + code.Make(code.OpGetGlobal, 0), + code.Make(code.OpReturnValue), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpSetGlobal, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpPop), + }, + }, + { + input: ` + fn() { + let num = 55; + num + } + `, + expectedConstants: []interface{}{ + 55, + []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpSetLocal, 0), + code.Make(code.OpGetLocal, 0), + code.Make(code.OpReturnValue), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 1), + code.Make(code.OpPop), + }, + }, + { + input: ` + fn() { + let a = 55; + let b = 77; + a + b + } + `, + expectedConstants: []interface{}{ + 55, + 77, + []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpSetLocal, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpSetLocal, 1), + code.Make(code.OpGetLocal, 0), + code.Make(code.OpGetLocal, 1), + code.Make(code.OpAdd), + code.Make(code.OpReturnValue), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 2), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestBuiltins(t *testing.T) { + tests := []compilerTestCase{ + { + input: ` + len([]); + push([], 1); + `, + expectedConstants: []interface{}{1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpGetBuiltin, 0), + code.Make(code.OpArray, 0), + code.Make(code.OpCall, 1), + code.Make(code.OpPop), + code.Make(code.OpGetBuiltin, 5), + code.Make(code.OpArray, 0), + code.Make(code.OpConstant, 0), + code.Make(code.OpCall, 2), + code.Make(code.OpPop), + }, + }, + { + input: `fn() { len([]) }`, + expectedConstants: []interface{}{ + []code.Instructions{ + code.Make(code.OpGetBuiltin, 0), + code.Make(code.OpArray, 0), + code.Make(code.OpCall, 1), + code.Make(code.OpReturnValue), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func parse(input string) *ast.Program { + l := lexer.New(input) + p := parser.New(l) + return p.ParseProgram() +} + +func testInstructions( + expected []code.Instructions, + actual code.Instructions, +) error { + concatted := concatInstructions(expected) + + if len(actual) != len(concatted) { + return fmt.Errorf("wrong instructions length.\nwant=%q\ngot =%q", + concatted, actual) + } + + for i, ins := range concatted { + if actual[i] != ins { + return fmt.Errorf("wrong instruction at %d.\nwant=%q\ngot =%q", + i, concatted, actual) + } + } + + return nil +} + +func concatInstructions(s []code.Instructions) code.Instructions { + out := code.Instructions{} + + for _, ins := range s { + out = append(out, ins...) + } + + return out +} + +func testConstants( + t *testing.T, + expected []interface{}, + actual []object.Object, +) error { + if len(expected) != len(actual) { + return fmt.Errorf("wrong number of constants. got=%d, want=%d", + len(actual), len(expected)) + } + + for i, constant := range expected { + switch constant := constant.(type) { + case string: + err := testStringObject(constant, actual[i]) + if err != nil { + return fmt.Errorf("constant %d - testStringObject failed: %s", + i, err) + } + case int: + err := testIntegerObject(int64(constant), actual[i]) + if err != nil { + return fmt.Errorf("constant %d - testIntegerObject failed: %s", + i, err) + } + case []code.Instructions: + fn, ok := actual[i].(*object.CompiledFunction) + if !ok { + return fmt.Errorf("constant %d - not a function: %T", + i, actual[i]) + } + + err := testInstructions(constant, fn.Instructions) + if err != nil { + return fmt.Errorf("constant %d - testInstructions failed: %s", + i, err) + } + } + } + + return nil +} + +func testIntegerObject(expected int64, actual object.Object) error { + result, ok := actual.(*object.Integer) + if !ok { + return fmt.Errorf("object is not Integer. got=%T (%+v)", + actual, actual) + } + + if result.Value != expected { + return fmt.Errorf("object has wrong value. got=%d, want=%d", + result.Value, expected) + } + + return nil +} + +func testStringObject(expected string, actual object.Object) error { + result, ok := actual.(*object.String) + if !ok { + return fmt.Errorf("object is not String. got=%T (%+v)", + actual, actual) + } + + if result.Value != expected { + return fmt.Errorf("object has wrong value. got=%q, want=%q", + result.Value, expected) + } + + return nil +} diff --git a/wcig_code_1_2/08/src/monkey/compiler/symbol_table.go b/wcig_code_1_2/08/src/monkey/compiler/symbol_table.go new file mode 100644 index 0000000..cebc6d5 --- /dev/null +++ b/wcig_code_1_2/08/src/monkey/compiler/symbol_table.go @@ -0,0 +1,61 @@ +package compiler + +type SymbolScope string + +const ( + LocalScope SymbolScope = "LOCAL" + GlobalScope SymbolScope = "GLOBAL" + BuiltinScope SymbolScope = "BUILTIN" +) + +type Symbol struct { + Name string + Scope SymbolScope + Index int +} + +type SymbolTable struct { + Outer *SymbolTable + + store map[string]Symbol + numDefinitions int +} + +func NewEnclosedSymbolTable(outer *SymbolTable) *SymbolTable { + s := NewSymbolTable() + s.Outer = outer + return s +} + +func NewSymbolTable() *SymbolTable { + s := make(map[string]Symbol) + return &SymbolTable{store: s} +} + +func (s *SymbolTable) Define(name string) Symbol { + symbol := Symbol{Name: name, Index: s.numDefinitions} + if s.Outer == nil { + symbol.Scope = GlobalScope + } else { + symbol.Scope = LocalScope + } + + s.store[name] = symbol + s.numDefinitions++ + return symbol +} + +func (s *SymbolTable) Resolve(name string) (Symbol, bool) { + obj, ok := s.store[name] + if !ok && s.Outer != nil { + obj, ok = s.Outer.Resolve(name) + return obj, ok + } + return obj, ok +} + +func (s *SymbolTable) DefineBuiltin(index int, name string) Symbol { + symbol := Symbol{Name: name, Index: index, Scope: BuiltinScope} + s.store[name] = symbol + return symbol +} diff --git a/wcig_code_1_2/08/src/monkey/compiler/symbol_table_test.go b/wcig_code_1_2/08/src/monkey/compiler/symbol_table_test.go new file mode 100644 index 0000000..8db29b6 --- /dev/null +++ b/wcig_code_1_2/08/src/monkey/compiler/symbol_table_test.go @@ -0,0 +1,185 @@ +package compiler + +import "testing" + +func TestDefine(t *testing.T) { + expected := map[string]Symbol{ + "a": Symbol{Name: "a", Scope: GlobalScope, Index: 0}, + "b": Symbol{Name: "b", Scope: GlobalScope, Index: 1}, + "c": Symbol{Name: "c", Scope: LocalScope, Index: 0}, + "d": Symbol{Name: "d", Scope: LocalScope, Index: 1}, + "e": Symbol{Name: "e", Scope: LocalScope, Index: 0}, + "f": Symbol{Name: "f", Scope: LocalScope, Index: 1}, + } + + global := NewSymbolTable() + + a := global.Define("a") + if a != expected["a"] { + t.Errorf("expected a=%+v, got=%+v", expected["a"], a) + } + + b := global.Define("b") + if b != expected["b"] { + t.Errorf("expected b=%+v, got=%+v", expected["b"], b) + } + + firstLocal := NewEnclosedSymbolTable(global) + + c := firstLocal.Define("c") + if c != expected["c"] { + t.Errorf("expected c=%+v, got=%+v", expected["c"], c) + } + + d := firstLocal.Define("d") + if d != expected["d"] { + t.Errorf("expected d=%+v, got=%+v", expected["d"], d) + } + + secondLocal := NewEnclosedSymbolTable(firstLocal) + + e := secondLocal.Define("e") + if e != expected["e"] { + t.Errorf("expected e=%+v, got=%+v", expected["e"], e) + } + + f := secondLocal.Define("f") + if f != expected["f"] { + t.Errorf("expected f=%+v, got=%+v", expected["f"], f) + } +} + +func TestResolveGlobal(t *testing.T) { + global := NewSymbolTable() + global.Define("a") + global.Define("b") + + expected := []Symbol{ + Symbol{Name: "a", Scope: GlobalScope, Index: 0}, + Symbol{Name: "b", Scope: GlobalScope, Index: 1}, + } + + for _, sym := range expected { + result, ok := global.Resolve(sym.Name) + if !ok { + t.Errorf("name %s not resolvable", sym.Name) + continue + } + if result != sym { + t.Errorf("expected %s to resolve to %+v, got=%+v", + sym.Name, sym, result) + } + } +} + +func TestResolveLocal(t *testing.T) { + global := NewSymbolTable() + global.Define("a") + global.Define("b") + + local := NewEnclosedSymbolTable(global) + local.Define("c") + local.Define("d") + + expected := []Symbol{ + Symbol{Name: "a", Scope: GlobalScope, Index: 0}, + Symbol{Name: "b", Scope: GlobalScope, Index: 1}, + Symbol{Name: "c", Scope: LocalScope, Index: 0}, + Symbol{Name: "d", Scope: LocalScope, Index: 1}, + } + + for _, sym := range expected { + result, ok := local.Resolve(sym.Name) + if !ok { + t.Errorf("name %s not resolvable", sym.Name) + continue + } + if result != sym { + t.Errorf("expected %s to resolve to %+v, got=%+v", + sym.Name, sym, result) + } + } +} + +func TestResolveNestedLocal(t *testing.T) { + global := NewSymbolTable() + global.Define("a") + global.Define("b") + + firstLocal := NewEnclosedSymbolTable(global) + firstLocal.Define("c") + firstLocal.Define("d") + + secondLocal := NewEnclosedSymbolTable(firstLocal) + secondLocal.Define("e") + secondLocal.Define("f") + + tests := []struct { + table *SymbolTable + expectedSymbols []Symbol + }{ + { + firstLocal, + []Symbol{ + Symbol{Name: "a", Scope: GlobalScope, Index: 0}, + Symbol{Name: "b", Scope: GlobalScope, Index: 1}, + Symbol{Name: "c", Scope: LocalScope, Index: 0}, + Symbol{Name: "d", Scope: LocalScope, Index: 1}, + }, + }, + { + secondLocal, + []Symbol{ + Symbol{Name: "a", Scope: GlobalScope, Index: 0}, + Symbol{Name: "b", Scope: GlobalScope, Index: 1}, + Symbol{Name: "e", Scope: LocalScope, Index: 0}, + Symbol{Name: "f", Scope: LocalScope, Index: 1}, + }, + }, + } + + for _, tt := range tests { + for _, sym := range tt.expectedSymbols { + result, ok := tt.table.Resolve(sym.Name) + if !ok { + t.Errorf("name %s not resolvable", sym.Name) + continue + } + if result != sym { + t.Errorf("expected %s to resolve to %+v, got=%+v", + sym.Name, sym, result) + } + } + } +} + +func TestDefineResolveBuiltins(t *testing.T) { + global := NewSymbolTable() + firstLocal := NewEnclosedSymbolTable(global) + secondLocal := NewEnclosedSymbolTable(firstLocal) + + expected := []Symbol{ + Symbol{Name: "a", Scope: BuiltinScope, Index: 0}, + Symbol{Name: "c", Scope: BuiltinScope, Index: 1}, + Symbol{Name: "e", Scope: BuiltinScope, Index: 2}, + Symbol{Name: "f", Scope: BuiltinScope, Index: 3}, + } + + for i, v := range expected { + global.DefineBuiltin(i, v.Name) + } + + for _, table := range []*SymbolTable{global, firstLocal, secondLocal} { + for _, sym := range expected { + result, ok := table.Resolve(sym.Name) + if !ok { + t.Errorf("name %s not resolvable", sym.Name) + continue + } + if result != sym { + t.Errorf("expected %s to resolve to %+v, got=%+v", + sym.Name, sym, result) + } + } + } +} diff --git a/wcig_code_1_2/08/src/monkey/evaluator/builtins.go b/wcig_code_1_2/08/src/monkey/evaluator/builtins.go new file mode 100644 index 0000000..088ef5d --- /dev/null +++ b/wcig_code_1_2/08/src/monkey/evaluator/builtins.go @@ -0,0 +1,14 @@ +package evaluator + +import ( + "monkey/object" +) + +var builtins = map[string]*object.Builtin{ + "len": object.GetBuiltinByName("len"), + "puts": object.GetBuiltinByName("puts"), + "first": object.GetBuiltinByName("first"), + "last": object.GetBuiltinByName("last"), + "rest": object.GetBuiltinByName("rest"), + "push": object.GetBuiltinByName("push"), +} diff --git a/wcig_code_1_2/08/src/monkey/evaluator/evaluator.go b/wcig_code_1_2/08/src/monkey/evaluator/evaluator.go new file mode 100644 index 0000000..84c9011 --- /dev/null +++ b/wcig_code_1_2/08/src/monkey/evaluator/evaluator.go @@ -0,0 +1,445 @@ +package evaluator + +import ( + "fmt" + "monkey/ast" + "monkey/object" +) + +var ( + NULL = &object.Null{} + TRUE = &object.Boolean{Value: true} + FALSE = &object.Boolean{Value: false} +) + +func Eval(node ast.Node, env *object.Environment) object.Object { + switch node := node.(type) { + + // Statements + case *ast.Program: + return evalProgram(node, env) + + case *ast.BlockStatement: + return evalBlockStatement(node, env) + + case *ast.ExpressionStatement: + return Eval(node.Expression, env) + + case *ast.ReturnStatement: + val := Eval(node.ReturnValue, env) + if isError(val) { + return val + } + return &object.ReturnValue{Value: val} + + case *ast.LetStatement: + val := Eval(node.Value, env) + if isError(val) { + return val + } + env.Set(node.Name.Value, val) + + // Expressions + case *ast.IntegerLiteral: + return &object.Integer{Value: node.Value} + + case *ast.StringLiteral: + return &object.String{Value: node.Value} + + case *ast.Boolean: + return nativeBoolToBooleanObject(node.Value) + + case *ast.PrefixExpression: + right := Eval(node.Right, env) + if isError(right) { + return right + } + return evalPrefixExpression(node.Operator, right) + + case *ast.InfixExpression: + left := Eval(node.Left, env) + if isError(left) { + return left + } + + right := Eval(node.Right, env) + if isError(right) { + return right + } + + return evalInfixExpression(node.Operator, left, right) + + case *ast.IfExpression: + return evalIfExpression(node, env) + + case *ast.Identifier: + return evalIdentifier(node, env) + + case *ast.FunctionLiteral: + params := node.Parameters + body := node.Body + return &object.Function{Parameters: params, Env: env, Body: body} + + case *ast.CallExpression: + function := Eval(node.Function, env) + if isError(function) { + return function + } + + args := evalExpressions(node.Arguments, env) + if len(args) == 1 && isError(args[0]) { + return args[0] + } + + return applyFunction(function, args) + + case *ast.ArrayLiteral: + elements := evalExpressions(node.Elements, env) + if len(elements) == 1 && isError(elements[0]) { + return elements[0] + } + return &object.Array{Elements: elements} + + case *ast.IndexExpression: + left := Eval(node.Left, env) + if isError(left) { + return left + } + index := Eval(node.Index, env) + if isError(index) { + return index + } + return evalIndexExpression(left, index) + + case *ast.HashLiteral: + return evalHashLiteral(node, env) + + } + + return nil +} + +func evalProgram(program *ast.Program, env *object.Environment) object.Object { + var result object.Object + + for _, statement := range program.Statements { + result = Eval(statement, env) + + switch result := result.(type) { + case *object.ReturnValue: + return result.Value + case *object.Error: + return result + } + } + + return result +} + +func evalBlockStatement( + block *ast.BlockStatement, + env *object.Environment, +) object.Object { + var result object.Object + + for _, statement := range block.Statements { + result = Eval(statement, env) + + if result != nil { + rt := result.Type() + if rt == object.RETURN_VALUE_OBJ || rt == object.ERROR_OBJ { + return result + } + } + } + + return result +} + +func nativeBoolToBooleanObject(input bool) *object.Boolean { + if input { + return TRUE + } + return FALSE +} + +func evalPrefixExpression(operator string, right object.Object) object.Object { + switch operator { + case "!": + return evalBangOperatorExpression(right) + case "-": + return evalMinusPrefixOperatorExpression(right) + default: + return newError("unknown operator: %s%s", operator, right.Type()) + } +} + +func evalInfixExpression( + operator string, + left, right object.Object, +) object.Object { + switch { + case left.Type() == object.INTEGER_OBJ && right.Type() == object.INTEGER_OBJ: + return evalIntegerInfixExpression(operator, left, right) + case left.Type() == object.STRING_OBJ && right.Type() == object.STRING_OBJ: + return evalStringInfixExpression(operator, left, right) + case operator == "==": + return nativeBoolToBooleanObject(left == right) + case operator == "!=": + return nativeBoolToBooleanObject(left != right) + case left.Type() != right.Type(): + return newError("type mismatch: %s %s %s", + left.Type(), operator, right.Type()) + default: + return newError("unknown operator: %s %s %s", + left.Type(), operator, right.Type()) + } +} + +func evalBangOperatorExpression(right object.Object) object.Object { + switch right { + case TRUE: + return FALSE + case FALSE: + return TRUE + case NULL: + return TRUE + default: + return FALSE + } +} + +func evalMinusPrefixOperatorExpression(right object.Object) object.Object { + if right.Type() != object.INTEGER_OBJ { + return newError("unknown operator: -%s", right.Type()) + } + + value := right.(*object.Integer).Value + return &object.Integer{Value: -value} +} + +func evalIntegerInfixExpression( + operator string, + left, right object.Object, +) object.Object { + leftVal := left.(*object.Integer).Value + rightVal := right.(*object.Integer).Value + + switch operator { + case "+": + return &object.Integer{Value: leftVal + rightVal} + case "-": + return &object.Integer{Value: leftVal - rightVal} + case "*": + return &object.Integer{Value: leftVal * rightVal} + case "/": + return &object.Integer{Value: leftVal / rightVal} + case "<": + return nativeBoolToBooleanObject(leftVal < rightVal) + case ">": + return nativeBoolToBooleanObject(leftVal > rightVal) + case "==": + return nativeBoolToBooleanObject(leftVal == rightVal) + case "!=": + return nativeBoolToBooleanObject(leftVal != rightVal) + default: + return newError("unknown operator: %s %s %s", + left.Type(), operator, right.Type()) + } +} + +func evalStringInfixExpression( + operator string, + left, right object.Object, +) object.Object { + if operator != "+" { + return newError("unknown operator: %s %s %s", + left.Type(), operator, right.Type()) + } + + leftVal := left.(*object.String).Value + rightVal := right.(*object.String).Value + return &object.String{Value: leftVal + rightVal} +} + +func evalIfExpression( + ie *ast.IfExpression, + env *object.Environment, +) object.Object { + condition := Eval(ie.Condition, env) + if isError(condition) { + return condition + } + + if isTruthy(condition) { + return Eval(ie.Consequence, env) + } else if ie.Alternative != nil { + return Eval(ie.Alternative, env) + } else { + return NULL + } +} + +func evalIdentifier( + node *ast.Identifier, + env *object.Environment, +) object.Object { + if val, ok := env.Get(node.Value); ok { + return val + } + + if builtin, ok := builtins[node.Value]; ok { + return builtin + } + + return newError("identifier not found: " + node.Value) +} + +func isTruthy(obj object.Object) bool { + switch obj { + case NULL: + return false + case TRUE: + return true + case FALSE: + return false + default: + return true + } +} + +func newError(format string, a ...interface{}) *object.Error { + return &object.Error{Message: fmt.Sprintf(format, a...)} +} + +func isError(obj object.Object) bool { + if obj != nil { + return obj.Type() == object.ERROR_OBJ + } + return false +} + +func evalExpressions( + exps []ast.Expression, + env *object.Environment, +) []object.Object { + var result []object.Object + + for _, e := range exps { + evaluated := Eval(e, env) + if isError(evaluated) { + return []object.Object{evaluated} + } + result = append(result, evaluated) + } + + return result +} + +func applyFunction(fn object.Object, args []object.Object) object.Object { + switch fn := fn.(type) { + + case *object.Function: + extendedEnv := extendFunctionEnv(fn, args) + evaluated := Eval(fn.Body, extendedEnv) + return unwrapReturnValue(evaluated) + + case *object.Builtin: + if result := fn.Fn(args...); result != nil { + return result + } + return NULL + + default: + return newError("not a function: %s", fn.Type()) + } +} + +func extendFunctionEnv( + fn *object.Function, + args []object.Object, +) *object.Environment { + env := object.NewEnclosedEnvironment(fn.Env) + + for paramIdx, param := range fn.Parameters { + env.Set(param.Value, args[paramIdx]) + } + + return env +} + +func unwrapReturnValue(obj object.Object) object.Object { + if returnValue, ok := obj.(*object.ReturnValue); ok { + return returnValue.Value + } + + return obj +} + +func evalIndexExpression(left, index object.Object) object.Object { + switch { + case left.Type() == object.ARRAY_OBJ && index.Type() == object.INTEGER_OBJ: + return evalArrayIndexExpression(left, index) + case left.Type() == object.HASH_OBJ: + return evalHashIndexExpression(left, index) + default: + return newError("index operator not supported: %s", left.Type()) + } +} + +func evalArrayIndexExpression(array, index object.Object) object.Object { + arrayObject := array.(*object.Array) + idx := index.(*object.Integer).Value + max := int64(len(arrayObject.Elements) - 1) + + if idx < 0 || idx > max { + return NULL + } + + return arrayObject.Elements[idx] +} + +func evalHashLiteral( + node *ast.HashLiteral, + env *object.Environment, +) object.Object { + pairs := make(map[object.HashKey]object.HashPair) + + for keyNode, valueNode := range node.Pairs { + key := Eval(keyNode, env) + if isError(key) { + return key + } + + hashKey, ok := key.(object.Hashable) + if !ok { + return newError("unusable as hash key: %s", key.Type()) + } + + value := Eval(valueNode, env) + if isError(value) { + return value + } + + hashed := hashKey.HashKey() + pairs[hashed] = object.HashPair{Key: key, Value: value} + } + + return &object.Hash{Pairs: pairs} +} + +func evalHashIndexExpression(hash, index object.Object) object.Object { + hashObject := hash.(*object.Hash) + + key, ok := index.(object.Hashable) + if !ok { + return newError("unusable as hash key: %s", index.Type()) + } + + pair, ok := hashObject.Pairs[key.HashKey()] + if !ok { + return NULL + } + + return pair.Value +} diff --git a/wcig_code_1_2/08/src/monkey/evaluator/evaluator_test.go b/wcig_code_1_2/08/src/monkey/evaluator/evaluator_test.go new file mode 100644 index 0000000..2304e6f --- /dev/null +++ b/wcig_code_1_2/08/src/monkey/evaluator/evaluator_test.go @@ -0,0 +1,629 @@ +package evaluator + +import ( + "monkey/lexer" + "monkey/object" + "monkey/parser" + "testing" +) + +func TestEvalIntegerExpression(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"5", 5}, + {"10", 10}, + {"-5", -5}, + {"-10", -10}, + {"5 + 5 + 5 + 5 - 10", 10}, + {"2 * 2 * 2 * 2 * 2", 32}, + {"-50 + 100 + -50", 0}, + {"5 * 2 + 10", 20}, + {"5 + 2 * 10", 25}, + {"20 + 2 * -10", 0}, + {"50 / 2 * 2 + 10", 60}, + {"2 * (5 + 10)", 30}, + {"3 * 3 * 3 + 10", 37}, + {"3 * (3 * 3) + 10", 37}, + {"(5 + 10 * 2 + 15 / 3) * 2 + -10", 50}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testIntegerObject(t, evaluated, tt.expected) + } +} + +func TestEvalBooleanExpression(t *testing.T) { + tests := []struct { + input string + expected bool + }{ + {"true", true}, + {"false", false}, + {"1 < 2", true}, + {"1 > 2", false}, + {"1 < 1", false}, + {"1 > 1", false}, + {"1 == 1", true}, + {"1 != 1", false}, + {"1 == 2", false}, + {"1 != 2", true}, + {"true == true", true}, + {"false == false", true}, + {"true == false", false}, + {"true != false", true}, + {"false != true", true}, + {"(1 < 2) == true", true}, + {"(1 < 2) == false", false}, + {"(1 > 2) == true", false}, + {"(1 > 2) == false", true}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testBooleanObject(t, evaluated, tt.expected) + } +} + +func TestBangOperator(t *testing.T) { + tests := []struct { + input string + expected bool + }{ + {"!true", false}, + {"!false", true}, + {"!5", false}, + {"!!true", true}, + {"!!false", false}, + {"!!5", true}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testBooleanObject(t, evaluated, tt.expected) + } +} + +func TestIfElseExpressions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + {"if (true) { 10 }", 10}, + {"if (false) { 10 }", nil}, + {"if (1) { 10 }", 10}, + {"if (1 < 2) { 10 }", 10}, + {"if (1 > 2) { 10 }", nil}, + {"if (1 > 2) { 10 } else { 20 }", 20}, + {"if (1 < 2) { 10 } else { 20 }", 10}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + integer, ok := tt.expected.(int) + if ok { + testIntegerObject(t, evaluated, int64(integer)) + } else { + testNullObject(t, evaluated) + } + } +} + +func TestReturnStatements(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"return 10;", 10}, + {"return 10; 9;", 10}, + {"return 2 * 5; 9;", 10}, + {"9; return 2 * 5; 9;", 10}, + {"if (10 > 1) { return 10; }", 10}, + { + ` +if (10 > 1) { + if (10 > 1) { + return 10; + } + + return 1; +} +`, + 10, + }, + { + ` +let f = fn(x) { + return x; + x + 10; +}; +f(10);`, + 10, + }, + { + ` +let f = fn(x) { + let result = x + 10; + return result; + return 10; +}; +f(10);`, + 20, + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testIntegerObject(t, evaluated, tt.expected) + } +} + +func TestErrorHandling(t *testing.T) { + tests := []struct { + input string + expectedMessage string + }{ + { + "5 + true;", + "type mismatch: INTEGER + BOOLEAN", + }, + { + "5 + true; 5;", + "type mismatch: INTEGER + BOOLEAN", + }, + { + "-true", + "unknown operator: -BOOLEAN", + }, + { + "true + false;", + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + "true + false + true + false;", + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + "5; true + false; 5", + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + `"Hello" - "World"`, + "unknown operator: STRING - STRING", + }, + { + "if (10 > 1) { true + false; }", + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + ` +if (10 > 1) { + if (10 > 1) { + return true + false; + } + + return 1; +} +`, + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + "foobar", + "identifier not found: foobar", + }, + { + `{"name": "Monkey"}[fn(x) { x }];`, + "unusable as hash key: FUNCTION", + }, + { + `999[1]`, + "index operator not supported: INTEGER", + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + + errObj, ok := evaluated.(*object.Error) + if !ok { + t.Errorf("no error object returned. got=%T(%+v)", + evaluated, evaluated) + continue + } + + if errObj.Message != tt.expectedMessage { + t.Errorf("wrong error message. expected=%q, got=%q", + tt.expectedMessage, errObj.Message) + } + } +} + +func TestLetStatements(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"let a = 5; a;", 5}, + {"let a = 5 * 5; a;", 25}, + {"let a = 5; let b = a; b;", 5}, + {"let a = 5; let b = a; let c = a + b + 5; c;", 15}, + } + + for _, tt := range tests { + testIntegerObject(t, testEval(tt.input), tt.expected) + } +} + +func TestFunctionObject(t *testing.T) { + input := "fn(x) { x + 2; };" + + evaluated := testEval(input) + fn, ok := evaluated.(*object.Function) + if !ok { + t.Fatalf("object is not Function. got=%T (%+v)", evaluated, evaluated) + } + + if len(fn.Parameters) != 1 { + t.Fatalf("function has wrong parameters. Parameters=%+v", + fn.Parameters) + } + + if fn.Parameters[0].String() != "x" { + t.Fatalf("parameter is not 'x'. got=%q", fn.Parameters[0]) + } + + expectedBody := "(x + 2)" + + if fn.Body.String() != expectedBody { + t.Fatalf("body is not %q. got=%q", expectedBody, fn.Body.String()) + } +} + +func TestFunctionApplication(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"let identity = fn(x) { x; }; identity(5);", 5}, + {"let identity = fn(x) { return x; }; identity(5);", 5}, + {"let double = fn(x) { x * 2; }; double(5);", 10}, + {"let add = fn(x, y) { x + y; }; add(5, 5);", 10}, + {"let add = fn(x, y) { x + y; }; add(5 + 5, add(5, 5));", 20}, + {"fn(x) { x; }(5)", 5}, + } + + for _, tt := range tests { + testIntegerObject(t, testEval(tt.input), tt.expected) + } +} + +func TestEnclosingEnvironments(t *testing.T) { + input := ` +let first = 10; +let second = 10; +let third = 10; + +let ourFunction = fn(first) { + let second = 20; + + first + second + third; +}; + +ourFunction(20) + first + second;` + + testIntegerObject(t, testEval(input), 70) +} + +func TestClosures(t *testing.T) { + input := ` +let newAdder = fn(x) { + fn(y) { x + y }; +}; + +let addTwo = newAdder(2); +addTwo(2);` + + testIntegerObject(t, testEval(input), 4) +} + +func TestStringLiteral(t *testing.T) { + input := `"Hello World!"` + + evaluated := testEval(input) + str, ok := evaluated.(*object.String) + if !ok { + t.Fatalf("object is not String. got=%T (%+v)", evaluated, evaluated) + } + + if str.Value != "Hello World!" { + t.Errorf("String has wrong value. got=%q", str.Value) + } +} + +func TestStringConcatenation(t *testing.T) { + input := `"Hello" + " " + "World!"` + + evaluated := testEval(input) + str, ok := evaluated.(*object.String) + if !ok { + t.Fatalf("object is not String. got=%T (%+v)", evaluated, evaluated) + } + + if str.Value != "Hello World!" { + t.Errorf("String has wrong value. got=%q", str.Value) + } +} + +func TestBuiltinFunctions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + {`len("")`, 0}, + {`len("four")`, 4}, + {`len("hello world")`, 11}, + {`len(1)`, "argument to `len` not supported, got INTEGER"}, + {`len("one", "two")`, "wrong number of arguments. got=2, want=1"}, + {`len([1, 2, 3])`, 3}, + {`len([])`, 0}, + {`puts("hello", "world!")`, nil}, + {`first([1, 2, 3])`, 1}, + {`first([])`, nil}, + {`first(1)`, "argument to `first` must be ARRAY, got INTEGER"}, + {`last([1, 2, 3])`, 3}, + {`last([])`, nil}, + {`last(1)`, "argument to `last` must be ARRAY, got INTEGER"}, + {`rest([1, 2, 3])`, []int{2, 3}}, + {`rest([])`, nil}, + {`push([], 1)`, []int{1}}, + {`push(1, 1)`, "argument to `push` must be ARRAY, got INTEGER"}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + + switch expected := tt.expected.(type) { + case int: + testIntegerObject(t, evaluated, int64(expected)) + case nil: + testNullObject(t, evaluated) + case string: + errObj, ok := evaluated.(*object.Error) + if !ok { + t.Errorf("object is not Error. got=%T (%+v)", + evaluated, evaluated) + continue + } + if errObj.Message != expected { + t.Errorf("wrong error message. expected=%q, got=%q", + expected, errObj.Message) + } + case []int: + array, ok := evaluated.(*object.Array) + if !ok { + t.Errorf("obj not Array. got=%T (%+v)", evaluated, evaluated) + continue + } + + if len(array.Elements) != len(expected) { + t.Errorf("wrong num of elements. want=%d, got=%d", + len(expected), len(array.Elements)) + continue + } + + for i, expectedElem := range expected { + testIntegerObject(t, array.Elements[i], int64(expectedElem)) + } + } + } +} + +func TestArrayLiterals(t *testing.T) { + input := "[1, 2 * 2, 3 + 3]" + + evaluated := testEval(input) + result, ok := evaluated.(*object.Array) + if !ok { + t.Fatalf("object is not Array. got=%T (%+v)", evaluated, evaluated) + } + + if len(result.Elements) != 3 { + t.Fatalf("array has wrong num of elements. got=%d", + len(result.Elements)) + } + + testIntegerObject(t, result.Elements[0], 1) + testIntegerObject(t, result.Elements[1], 4) + testIntegerObject(t, result.Elements[2], 6) +} + +func TestArrayIndexExpressions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + { + "[1, 2, 3][0]", + 1, + }, + { + "[1, 2, 3][1]", + 2, + }, + { + "[1, 2, 3][2]", + 3, + }, + { + "let i = 0; [1][i];", + 1, + }, + { + "[1, 2, 3][1 + 1];", + 3, + }, + { + "let myArray = [1, 2, 3]; myArray[2];", + 3, + }, + { + "let myArray = [1, 2, 3]; myArray[0] + myArray[1] + myArray[2];", + 6, + }, + { + "let myArray = [1, 2, 3]; let i = myArray[0]; myArray[i]", + 2, + }, + { + "[1, 2, 3][3]", + nil, + }, + { + "[1, 2, 3][-1]", + nil, + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + integer, ok := tt.expected.(int) + if ok { + testIntegerObject(t, evaluated, int64(integer)) + } else { + testNullObject(t, evaluated) + } + } +} + +func TestHashLiterals(t *testing.T) { + input := `let two = "two"; + { + "one": 10 - 9, + two: 1 + 1, + "thr" + "ee": 6 / 2, + 4: 4, + true: 5, + false: 6 + }` + + evaluated := testEval(input) + result, ok := evaluated.(*object.Hash) + if !ok { + t.Fatalf("Eval didn't return Hash. got=%T (%+v)", evaluated, evaluated) + } + + expected := map[object.HashKey]int64{ + (&object.String{Value: "one"}).HashKey(): 1, + (&object.String{Value: "two"}).HashKey(): 2, + (&object.String{Value: "three"}).HashKey(): 3, + (&object.Integer{Value: 4}).HashKey(): 4, + TRUE.HashKey(): 5, + FALSE.HashKey(): 6, + } + + if len(result.Pairs) != len(expected) { + t.Fatalf("Hash has wrong num of pairs. got=%d", len(result.Pairs)) + } + + for expectedKey, expectedValue := range expected { + pair, ok := result.Pairs[expectedKey] + if !ok { + t.Errorf("no pair for given key in Pairs") + } + + testIntegerObject(t, pair.Value, expectedValue) + } +} + +func TestHashIndexExpressions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + { + `{"foo": 5}["foo"]`, + 5, + }, + { + `{"foo": 5}["bar"]`, + nil, + }, + { + `let key = "foo"; {"foo": 5}[key]`, + 5, + }, + { + `{}["foo"]`, + nil, + }, + { + `{5: 5}[5]`, + 5, + }, + { + `{true: 5}[true]`, + 5, + }, + { + `{false: 5}[false]`, + 5, + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + integer, ok := tt.expected.(int) + if ok { + testIntegerObject(t, evaluated, int64(integer)) + } else { + testNullObject(t, evaluated) + } + } +} +func testEval(input string) object.Object { + l := lexer.New(input) + p := parser.New(l) + program := p.ParseProgram() + env := object.NewEnvironment() + + return Eval(program, env) +} + +func testIntegerObject(t *testing.T, obj object.Object, expected int64) bool { + result, ok := obj.(*object.Integer) + if !ok { + t.Errorf("object is not Integer. got=%T (%+v)", obj, obj) + return false + } + if result.Value != expected { + t.Errorf("object has wrong value. got=%d, want=%d", + result.Value, expected) + return false + } + + return true +} + +func testBooleanObject(t *testing.T, obj object.Object, expected bool) bool { + result, ok := obj.(*object.Boolean) + if !ok { + t.Errorf("object is not Boolean. got=%T (%+v)", obj, obj) + return false + } + if result.Value != expected { + t.Errorf("object has wrong value. got=%t, want=%t", + result.Value, expected) + return false + } + return true +} + +func testNullObject(t *testing.T, obj object.Object) bool { + if obj != NULL { + t.Errorf("object is not NULL. got=%T (%+v)", obj, obj) + return false + } + return true +} diff --git a/wcig_code_1_2/08/src/monkey/go.mod b/wcig_code_1_2/08/src/monkey/go.mod new file mode 100644 index 0000000..3064854 --- /dev/null +++ b/wcig_code_1_2/08/src/monkey/go.mod @@ -0,0 +1,3 @@ +module monkey + +go 1.14 diff --git a/wcig_code_1_2/08/src/monkey/lexer/lexer.go b/wcig_code_1_2/08/src/monkey/lexer/lexer.go new file mode 100644 index 0000000..db4f5f7 --- /dev/null +++ b/wcig_code_1_2/08/src/monkey/lexer/lexer.go @@ -0,0 +1,157 @@ +package lexer + +import "monkey/token" + +type Lexer struct { + input string + position int // current position in input (points to current char) + readPosition int // current reading position in input (after current char) + ch byte // current char under examination +} + +func New(input string) *Lexer { + l := &Lexer{input: input} + l.readChar() + return l +} + +func (l *Lexer) NextToken() token.Token { + var tok token.Token + + l.skipWhitespace() + + switch l.ch { + case '=': + if l.peekChar() == '=' { + ch := l.ch + l.readChar() + literal := string(ch) + string(l.ch) + tok = token.Token{Type: token.EQ, Literal: literal} + } else { + tok = newToken(token.ASSIGN, l.ch) + } + case '+': + tok = newToken(token.PLUS, l.ch) + case '-': + tok = newToken(token.MINUS, l.ch) + case '!': + if l.peekChar() == '=' { + ch := l.ch + l.readChar() + literal := string(ch) + string(l.ch) + tok = token.Token{Type: token.NOT_EQ, Literal: literal} + } else { + tok = newToken(token.BANG, l.ch) + } + case '/': + tok = newToken(token.SLASH, l.ch) + case '*': + tok = newToken(token.ASTERISK, l.ch) + case '<': + tok = newToken(token.LT, l.ch) + case '>': + tok = newToken(token.GT, l.ch) + case ';': + tok = newToken(token.SEMICOLON, l.ch) + case ':': + tok = newToken(token.COLON, l.ch) + case ',': + tok = newToken(token.COMMA, l.ch) + case '{': + tok = newToken(token.LBRACE, l.ch) + case '}': + tok = newToken(token.RBRACE, l.ch) + case '(': + tok = newToken(token.LPAREN, l.ch) + case ')': + tok = newToken(token.RPAREN, l.ch) + case '"': + tok.Type = token.STRING + tok.Literal = l.readString() + case '[': + tok = newToken(token.LBRACKET, l.ch) + case ']': + tok = newToken(token.RBRACKET, l.ch) + case 0: + tok.Literal = "" + tok.Type = token.EOF + default: + if isLetter(l.ch) { + tok.Literal = l.readIdentifier() + tok.Type = token.LookupIdent(tok.Literal) + return tok + } else if isDigit(l.ch) { + tok.Type = token.INT + tok.Literal = l.readNumber() + return tok + } else { + tok = newToken(token.ILLEGAL, l.ch) + } + } + + l.readChar() + return tok +} + +func (l *Lexer) skipWhitespace() { + for l.ch == ' ' || l.ch == '\t' || l.ch == '\n' || l.ch == '\r' { + l.readChar() + } +} + +func (l *Lexer) readChar() { + if l.readPosition >= len(l.input) { + l.ch = 0 + } else { + l.ch = l.input[l.readPosition] + } + l.position = l.readPosition + l.readPosition += 1 +} + +func (l *Lexer) peekChar() byte { + if l.readPosition >= len(l.input) { + return 0 + } else { + return l.input[l.readPosition] + } +} + +func (l *Lexer) readIdentifier() string { + position := l.position + for isLetter(l.ch) { + l.readChar() + } + return l.input[position:l.position] +} + +func (l *Lexer) readNumber() string { + position := l.position + for isDigit(l.ch) { + l.readChar() + } + return l.input[position:l.position] +} + +func (l *Lexer) readString() string { + position := l.position + 1 + for { + l.readChar() + if l.ch == '"' || l.ch == 0 { + break + } + } + return l.input[position:l.position] +} + +func isLetter(ch byte) bool { + return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' +} + +func isDigit(ch byte) bool { + return '0' <= ch && ch <= '9' +} + +func newToken(tokenType token.TokenType, ch byte) token.Token { + return token.Token{Type: tokenType, Literal: string(ch)} +} diff --git a/wcig_code_1_2/08/src/monkey/lexer/lexer_test.go b/wcig_code_1_2/08/src/monkey/lexer/lexer_test.go new file mode 100644 index 0000000..e4e64a4 --- /dev/null +++ b/wcig_code_1_2/08/src/monkey/lexer/lexer_test.go @@ -0,0 +1,143 @@ +package lexer + +import ( + "testing" + + "monkey/token" +) + +func TestNextToken(t *testing.T) { + input := `let five = 5; +let ten = 10; + +let add = fn(x, y) { + x + y; +}; + +let result = add(five, ten); +!-/*5; +5 < 10 > 5; + +if (5 < 10) { + return true; +} else { + return false; +} + +10 == 10; +10 != 9; +"foobar" +"foo bar" +[1, 2]; +{"foo": "bar"} +` + + tests := []struct { + expectedType token.TokenType + expectedLiteral string + }{ + {token.LET, "let"}, + {token.IDENT, "five"}, + {token.ASSIGN, "="}, + {token.INT, "5"}, + {token.SEMICOLON, ";"}, + {token.LET, "let"}, + {token.IDENT, "ten"}, + {token.ASSIGN, "="}, + {token.INT, "10"}, + {token.SEMICOLON, ";"}, + {token.LET, "let"}, + {token.IDENT, "add"}, + {token.ASSIGN, "="}, + {token.FUNCTION, "fn"}, + {token.LPAREN, "("}, + {token.IDENT, "x"}, + {token.COMMA, ","}, + {token.IDENT, "y"}, + {token.RPAREN, ")"}, + {token.LBRACE, "{"}, + {token.IDENT, "x"}, + {token.PLUS, "+"}, + {token.IDENT, "y"}, + {token.SEMICOLON, ";"}, + {token.RBRACE, "}"}, + {token.SEMICOLON, ";"}, + {token.LET, "let"}, + {token.IDENT, "result"}, + {token.ASSIGN, "="}, + {token.IDENT, "add"}, + {token.LPAREN, "("}, + {token.IDENT, "five"}, + {token.COMMA, ","}, + {token.IDENT, "ten"}, + {token.RPAREN, ")"}, + {token.SEMICOLON, ";"}, + {token.BANG, "!"}, + {token.MINUS, "-"}, + {token.SLASH, "/"}, + {token.ASTERISK, "*"}, + {token.INT, "5"}, + {token.SEMICOLON, ";"}, + {token.INT, "5"}, + {token.LT, "<"}, + {token.INT, "10"}, + {token.GT, ">"}, + {token.INT, "5"}, + {token.SEMICOLON, ";"}, + {token.IF, "if"}, + {token.LPAREN, "("}, + {token.INT, "5"}, + {token.LT, "<"}, + {token.INT, "10"}, + {token.RPAREN, ")"}, + {token.LBRACE, "{"}, + {token.RETURN, "return"}, + {token.TRUE, "true"}, + {token.SEMICOLON, ";"}, + {token.RBRACE, "}"}, + {token.ELSE, "else"}, + {token.LBRACE, "{"}, + {token.RETURN, "return"}, + {token.FALSE, "false"}, + {token.SEMICOLON, ";"}, + {token.RBRACE, "}"}, + {token.INT, "10"}, + {token.EQ, "=="}, + {token.INT, "10"}, + {token.SEMICOLON, ";"}, + {token.INT, "10"}, + {token.NOT_EQ, "!="}, + {token.INT, "9"}, + {token.SEMICOLON, ";"}, + {token.STRING, "foobar"}, + {token.STRING, "foo bar"}, + {token.LBRACKET, "["}, + {token.INT, "1"}, + {token.COMMA, ","}, + {token.INT, "2"}, + {token.RBRACKET, "]"}, + {token.SEMICOLON, ";"}, + {token.LBRACE, "{"}, + {token.STRING, "foo"}, + {token.COLON, ":"}, + {token.STRING, "bar"}, + {token.RBRACE, "}"}, + {token.EOF, ""}, + } + + l := New(input) + + for i, tt := range tests { + tok := l.NextToken() + + if tok.Type != tt.expectedType { + t.Fatalf("tests[%d] - tokentype wrong. expected=%q, got=%q", + i, tt.expectedType, tok.Type) + } + + if tok.Literal != tt.expectedLiteral { + t.Fatalf("tests[%d] - literal wrong. expected=%q, got=%q", + i, tt.expectedLiteral, tok.Literal) + } + } +} diff --git a/wcig_code_1_2/08/src/monkey/main.go b/wcig_code_1_2/08/src/monkey/main.go new file mode 100644 index 0000000..1d27138 --- /dev/null +++ b/wcig_code_1_2/08/src/monkey/main.go @@ -0,0 +1,19 @@ +package main + +import ( + "fmt" + "monkey/repl" + "os" + "os/user" +) + +func main() { + user, err := user.Current() + if err != nil { + panic(err) + } + fmt.Printf("Hello %s! This is the Monkey programming language!\n", + user.Username) + fmt.Printf("Feel free to type in commands\n") + repl.Start(os.Stdin, os.Stdout) +} diff --git a/wcig_code_1_2/08/src/monkey/object/builtins.go b/wcig_code_1_2/08/src/monkey/object/builtins.go new file mode 100644 index 0000000..28dca2d --- /dev/null +++ b/wcig_code_1_2/08/src/monkey/object/builtins.go @@ -0,0 +1,143 @@ +package object + +import "fmt" + +var Builtins = []struct { + Name string + Builtin *Builtin +}{ + { + "len", + &Builtin{Fn: func(args ...Object) Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", + len(args)) + } + + switch arg := args[0].(type) { + case *Array: + return &Integer{Value: int64(len(arg.Elements))} + case *String: + return &Integer{Value: int64(len(arg.Value))} + default: + return newError("argument to `len` not supported, got %s", + args[0].Type()) + } + }, + }, + }, + { + "puts", + &Builtin{Fn: func(args ...Object) Object { + for _, arg := range args { + fmt.Println(arg.Inspect()) + } + + return nil + }, + }, + }, + { + "first", + &Builtin{Fn: func(args ...Object) Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", + len(args)) + } + if args[0].Type() != ARRAY_OBJ { + return newError("argument to `first` must be ARRAY, got %s", + args[0].Type()) + } + + arr := args[0].(*Array) + if len(arr.Elements) > 0 { + return arr.Elements[0] + } + + return nil + }, + }, + }, + { + "last", + &Builtin{Fn: func(args ...Object) Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", + len(args)) + } + if args[0].Type() != ARRAY_OBJ { + return newError("argument to `last` must be ARRAY, got %s", + args[0].Type()) + } + + arr := args[0].(*Array) + length := len(arr.Elements) + if length > 0 { + return arr.Elements[length-1] + } + + return nil + }, + }, + }, + { + "rest", + &Builtin{Fn: func(args ...Object) Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", + len(args)) + } + if args[0].Type() != ARRAY_OBJ { + return newError("argument to `rest` must be ARRAY, got %s", + args[0].Type()) + } + + arr := args[0].(*Array) + length := len(arr.Elements) + if length > 0 { + newElements := make([]Object, length-1, length-1) + copy(newElements, arr.Elements[1:length]) + return &Array{Elements: newElements} + } + + return nil + }, + }, + }, + { + "push", + &Builtin{Fn: func(args ...Object) Object { + if len(args) != 2 { + return newError("wrong number of arguments. got=%d, want=2", + len(args)) + } + if args[0].Type() != ARRAY_OBJ { + return newError("argument to `push` must be ARRAY, got %s", + args[0].Type()) + } + + arr := args[0].(*Array) + length := len(arr.Elements) + + newElements := make([]Object, length+1, length+1) + copy(newElements, arr.Elements) + newElements[length] = args[1] + + return &Array{Elements: newElements} + }, + }, + }, +} + +func newError(format string, a ...interface{}) *Error { + return &Error{Message: fmt.Sprintf(format, a...)} +} + +func GetBuiltinByName(name string) *Builtin { + for _, def := range Builtins { + if def.Name == name { + return def.Builtin + } + } + return nil +} diff --git a/wcig_code_1_2/08/src/monkey/object/environment.go b/wcig_code_1_2/08/src/monkey/object/environment.go new file mode 100644 index 0000000..6f31070 --- /dev/null +++ b/wcig_code_1_2/08/src/monkey/object/environment.go @@ -0,0 +1,30 @@ +package object + +func NewEnclosedEnvironment(outer *Environment) *Environment { + env := NewEnvironment() + env.outer = outer + return env +} + +func NewEnvironment() *Environment { + s := make(map[string]Object) + return &Environment{store: s, outer: nil} +} + +type Environment struct { + store map[string]Object + outer *Environment +} + +func (e *Environment) Get(name string) (Object, bool) { + obj, ok := e.store[name] + if !ok && e.outer != nil { + obj, ok = e.outer.Get(name) + } + return obj, ok +} + +func (e *Environment) Set(name string, val Object) Object { + e.store[name] = val + return val +} diff --git a/wcig_code_1_2/08/src/monkey/object/object.go b/wcig_code_1_2/08/src/monkey/object/object.go new file mode 100644 index 0000000..9bdb4f4 --- /dev/null +++ b/wcig_code_1_2/08/src/monkey/object/object.go @@ -0,0 +1,196 @@ +package object + +import ( + "bytes" + "fmt" + "hash/fnv" + "monkey/ast" + "monkey/code" + "strings" +) + +type BuiltinFunction func(args ...Object) Object + +type ObjectType string + +const ( + NULL_OBJ = "NULL" + ERROR_OBJ = "ERROR" + + INTEGER_OBJ = "INTEGER" + BOOLEAN_OBJ = "BOOLEAN" + STRING_OBJ = "STRING" + + RETURN_VALUE_OBJ = "RETURN_VALUE" + + FUNCTION_OBJ = "FUNCTION" + BUILTIN_OBJ = "BUILTIN" + + ARRAY_OBJ = "ARRAY" + HASH_OBJ = "HASH" + + COMPILED_FUNCTION_OBJ = "COMPILED_FUNCTION_OBJ" +) + +type HashKey struct { + Type ObjectType + Value uint64 +} + +type Hashable interface { + HashKey() HashKey +} + +type Object interface { + Type() ObjectType + Inspect() string +} + +type Integer struct { + Value int64 +} + +func (i *Integer) Type() ObjectType { return INTEGER_OBJ } +func (i *Integer) Inspect() string { return fmt.Sprintf("%d", i.Value) } +func (i *Integer) HashKey() HashKey { + return HashKey{Type: i.Type(), Value: uint64(i.Value)} +} + +type Boolean struct { + Value bool +} + +func (b *Boolean) Type() ObjectType { return BOOLEAN_OBJ } +func (b *Boolean) Inspect() string { return fmt.Sprintf("%t", b.Value) } +func (b *Boolean) HashKey() HashKey { + var value uint64 + + if b.Value { + value = 1 + } else { + value = 0 + } + + return HashKey{Type: b.Type(), Value: value} +} + +type Null struct{} + +func (n *Null) Type() ObjectType { return NULL_OBJ } +func (n *Null) Inspect() string { return "null" } + +type ReturnValue struct { + Value Object +} + +func (rv *ReturnValue) Type() ObjectType { return RETURN_VALUE_OBJ } +func (rv *ReturnValue) Inspect() string { return rv.Value.Inspect() } + +type Error struct { + Message string +} + +func (e *Error) Type() ObjectType { return ERROR_OBJ } +func (e *Error) Inspect() string { return "ERROR: " + e.Message } + +type Function struct { + Parameters []*ast.Identifier + Body *ast.BlockStatement + Env *Environment +} + +func (f *Function) Type() ObjectType { return FUNCTION_OBJ } +func (f *Function) Inspect() string { + var out bytes.Buffer + + params := []string{} + for _, p := range f.Parameters { + params = append(params, p.String()) + } + + out.WriteString("fn") + out.WriteString("(") + out.WriteString(strings.Join(params, ", ")) + out.WriteString(") {\n") + out.WriteString(f.Body.String()) + out.WriteString("\n}") + + return out.String() +} + +type String struct { + Value string +} + +func (s *String) Type() ObjectType { return STRING_OBJ } +func (s *String) Inspect() string { return s.Value } +func (s *String) HashKey() HashKey { + h := fnv.New64a() + h.Write([]byte(s.Value)) + + return HashKey{Type: s.Type(), Value: h.Sum64()} +} + +type Builtin struct { + Fn BuiltinFunction +} + +func (b *Builtin) Type() ObjectType { return BUILTIN_OBJ } +func (b *Builtin) Inspect() string { return "builtin function" } + +type Array struct { + Elements []Object +} + +func (ao *Array) Type() ObjectType { return ARRAY_OBJ } +func (ao *Array) Inspect() string { + var out bytes.Buffer + + elements := []string{} + for _, e := range ao.Elements { + elements = append(elements, e.Inspect()) + } + + out.WriteString("[") + out.WriteString(strings.Join(elements, ", ")) + out.WriteString("]") + + return out.String() +} + +type HashPair struct { + Key Object + Value Object +} + +type Hash struct { + Pairs map[HashKey]HashPair +} + +func (h *Hash) Type() ObjectType { return HASH_OBJ } +func (h *Hash) Inspect() string { + var out bytes.Buffer + + pairs := []string{} + for _, pair := range h.Pairs { + pairs = append(pairs, fmt.Sprintf("%s: %s", + pair.Key.Inspect(), pair.Value.Inspect())) + } + + out.WriteString("{") + out.WriteString(strings.Join(pairs, ", ")) + out.WriteString("}") + + return out.String() +} + +type CompiledFunction struct { + Instructions code.Instructions + NumLocals int + NumParameters int +} + +func (cf *CompiledFunction) Type() ObjectType { return COMPILED_FUNCTION_OBJ } +func (cf *CompiledFunction) Inspect() string { + return fmt.Sprintf("CompiledFunction[%p]", cf) +} diff --git a/wcig_code_1_2/08/src/monkey/object/object_test.go b/wcig_code_1_2/08/src/monkey/object/object_test.go new file mode 100644 index 0000000..63228a2 --- /dev/null +++ b/wcig_code_1_2/08/src/monkey/object/object_test.go @@ -0,0 +1,60 @@ +package object + +import "testing" + +func TestStringHashKey(t *testing.T) { + hello1 := &String{Value: "Hello World"} + hello2 := &String{Value: "Hello World"} + diff1 := &String{Value: "My name is johnny"} + diff2 := &String{Value: "My name is johnny"} + + if hello1.HashKey() != hello2.HashKey() { + t.Errorf("strings with same content have different hash keys") + } + + if diff1.HashKey() != diff2.HashKey() { + t.Errorf("strings with same content have different hash keys") + } + + if hello1.HashKey() == diff1.HashKey() { + t.Errorf("strings with different content have same hash keys") + } +} + +func TestBooleanHashKey(t *testing.T) { + true1 := &Boolean{Value: true} + true2 := &Boolean{Value: true} + false1 := &Boolean{Value: false} + false2 := &Boolean{Value: false} + + if true1.HashKey() != true2.HashKey() { + t.Errorf("trues do not have same hash key") + } + + if false1.HashKey() != false2.HashKey() { + t.Errorf("falses do not have same hash key") + } + + if true1.HashKey() == false1.HashKey() { + t.Errorf("true has same hash key as false") + } +} + +func TestIntegerHashKey(t *testing.T) { + one1 := &Integer{Value: 1} + one2 := &Integer{Value: 1} + two1 := &Integer{Value: 2} + two2 := &Integer{Value: 2} + + if one1.HashKey() != one2.HashKey() { + t.Errorf("integers with same content have twoerent hash keys") + } + + if two1.HashKey() != two2.HashKey() { + t.Errorf("integers with same content have twoerent hash keys") + } + + if one1.HashKey() == two1.HashKey() { + t.Errorf("integers with twoerent content have same hash keys") + } +} diff --git a/wcig_code_1_2/08/src/monkey/parser/parser.go b/wcig_code_1_2/08/src/monkey/parser/parser.go new file mode 100644 index 0000000..0a1f19c --- /dev/null +++ b/wcig_code_1_2/08/src/monkey/parser/parser.go @@ -0,0 +1,491 @@ +package parser + +import ( + "fmt" + "monkey/ast" + "monkey/lexer" + "monkey/token" + "strconv" +) + +const ( + _ int = iota + LOWEST + EQUALS // == + LESSGREATER // > or < + SUM // + + PRODUCT // * + PREFIX // -X or !X + CALL // myFunction(X) + INDEX // array[index] +) + +var precedences = map[token.TokenType]int{ + token.EQ: EQUALS, + token.NOT_EQ: EQUALS, + token.LT: LESSGREATER, + token.GT: LESSGREATER, + token.PLUS: SUM, + token.MINUS: SUM, + token.SLASH: PRODUCT, + token.ASTERISK: PRODUCT, + token.LPAREN: CALL, + token.LBRACKET: INDEX, +} + +type ( + prefixParseFn func() ast.Expression + infixParseFn func(ast.Expression) ast.Expression +) + +type Parser struct { + l *lexer.Lexer + errors []string + + curToken token.Token + peekToken token.Token + + prefixParseFns map[token.TokenType]prefixParseFn + infixParseFns map[token.TokenType]infixParseFn +} + +func New(l *lexer.Lexer) *Parser { + p := &Parser{ + l: l, + errors: []string{}, + } + + p.prefixParseFns = make(map[token.TokenType]prefixParseFn) + p.registerPrefix(token.IDENT, p.parseIdentifier) + p.registerPrefix(token.INT, p.parseIntegerLiteral) + p.registerPrefix(token.STRING, p.parseStringLiteral) + p.registerPrefix(token.BANG, p.parsePrefixExpression) + p.registerPrefix(token.MINUS, p.parsePrefixExpression) + p.registerPrefix(token.TRUE, p.parseBoolean) + p.registerPrefix(token.FALSE, p.parseBoolean) + p.registerPrefix(token.LPAREN, p.parseGroupedExpression) + p.registerPrefix(token.IF, p.parseIfExpression) + p.registerPrefix(token.FUNCTION, p.parseFunctionLiteral) + p.registerPrefix(token.LBRACKET, p.parseArrayLiteral) + p.registerPrefix(token.LBRACE, p.parseHashLiteral) + + p.infixParseFns = make(map[token.TokenType]infixParseFn) + p.registerInfix(token.PLUS, p.parseInfixExpression) + p.registerInfix(token.MINUS, p.parseInfixExpression) + p.registerInfix(token.SLASH, p.parseInfixExpression) + p.registerInfix(token.ASTERISK, p.parseInfixExpression) + p.registerInfix(token.EQ, p.parseInfixExpression) + p.registerInfix(token.NOT_EQ, p.parseInfixExpression) + p.registerInfix(token.LT, p.parseInfixExpression) + p.registerInfix(token.GT, p.parseInfixExpression) + + p.registerInfix(token.LPAREN, p.parseCallExpression) + p.registerInfix(token.LBRACKET, p.parseIndexExpression) + + // Read two tokens, so curToken and peekToken are both set + p.nextToken() + p.nextToken() + + return p +} + +func (p *Parser) nextToken() { + p.curToken = p.peekToken + p.peekToken = p.l.NextToken() +} + +func (p *Parser) curTokenIs(t token.TokenType) bool { + return p.curToken.Type == t +} + +func (p *Parser) peekTokenIs(t token.TokenType) bool { + return p.peekToken.Type == t +} + +func (p *Parser) expectPeek(t token.TokenType) bool { + if p.peekTokenIs(t) { + p.nextToken() + return true + } else { + p.peekError(t) + return false + } +} + +func (p *Parser) Errors() []string { + return p.errors +} + +func (p *Parser) peekError(t token.TokenType) { + msg := fmt.Sprintf("expected next token to be %s, got %s instead", + t, p.peekToken.Type) + p.errors = append(p.errors, msg) +} + +func (p *Parser) noPrefixParseFnError(t token.TokenType) { + msg := fmt.Sprintf("no prefix parse function for %s found", t) + p.errors = append(p.errors, msg) +} + +func (p *Parser) ParseProgram() *ast.Program { + program := &ast.Program{} + program.Statements = []ast.Statement{} + + for !p.curTokenIs(token.EOF) { + stmt := p.parseStatement() + if stmt != nil { + program.Statements = append(program.Statements, stmt) + } + p.nextToken() + } + + return program +} + +func (p *Parser) parseStatement() ast.Statement { + switch p.curToken.Type { + case token.LET: + return p.parseLetStatement() + case token.RETURN: + return p.parseReturnStatement() + default: + return p.parseExpressionStatement() + } +} + +func (p *Parser) parseLetStatement() *ast.LetStatement { + stmt := &ast.LetStatement{Token: p.curToken} + + if !p.expectPeek(token.IDENT) { + return nil + } + + stmt.Name = &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + + if !p.expectPeek(token.ASSIGN) { + return nil + } + + p.nextToken() + + stmt.Value = p.parseExpression(LOWEST) + + if p.peekTokenIs(token.SEMICOLON) { + p.nextToken() + } + + return stmt +} + +func (p *Parser) parseReturnStatement() *ast.ReturnStatement { + stmt := &ast.ReturnStatement{Token: p.curToken} + + p.nextToken() + + stmt.ReturnValue = p.parseExpression(LOWEST) + + if p.peekTokenIs(token.SEMICOLON) { + p.nextToken() + } + + return stmt +} + +func (p *Parser) parseExpressionStatement() *ast.ExpressionStatement { + stmt := &ast.ExpressionStatement{Token: p.curToken} + + stmt.Expression = p.parseExpression(LOWEST) + + if p.peekTokenIs(token.SEMICOLON) { + p.nextToken() + } + + return stmt +} + +func (p *Parser) parseExpression(precedence int) ast.Expression { + prefix := p.prefixParseFns[p.curToken.Type] + if prefix == nil { + p.noPrefixParseFnError(p.curToken.Type) + return nil + } + leftExp := prefix() + + for !p.peekTokenIs(token.SEMICOLON) && precedence < p.peekPrecedence() { + infix := p.infixParseFns[p.peekToken.Type] + if infix == nil { + return leftExp + } + + p.nextToken() + + leftExp = infix(leftExp) + } + + return leftExp +} + +func (p *Parser) peekPrecedence() int { + if p, ok := precedences[p.peekToken.Type]; ok { + return p + } + + return LOWEST +} + +func (p *Parser) curPrecedence() int { + if p, ok := precedences[p.curToken.Type]; ok { + return p + } + + return LOWEST +} + +func (p *Parser) parseIdentifier() ast.Expression { + return &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} +} + +func (p *Parser) parseIntegerLiteral() ast.Expression { + lit := &ast.IntegerLiteral{Token: p.curToken} + + value, err := strconv.ParseInt(p.curToken.Literal, 0, 64) + if err != nil { + msg := fmt.Sprintf("could not parse %q as integer", p.curToken.Literal) + p.errors = append(p.errors, msg) + return nil + } + + lit.Value = value + + return lit +} + +func (p *Parser) parseStringLiteral() ast.Expression { + return &ast.StringLiteral{Token: p.curToken, Value: p.curToken.Literal} +} + +func (p *Parser) parsePrefixExpression() ast.Expression { + expression := &ast.PrefixExpression{ + Token: p.curToken, + Operator: p.curToken.Literal, + } + + p.nextToken() + + expression.Right = p.parseExpression(PREFIX) + + return expression +} + +func (p *Parser) parseInfixExpression(left ast.Expression) ast.Expression { + expression := &ast.InfixExpression{ + Token: p.curToken, + Operator: p.curToken.Literal, + Left: left, + } + + precedence := p.curPrecedence() + p.nextToken() + expression.Right = p.parseExpression(precedence) + + return expression +} + +func (p *Parser) parseBoolean() ast.Expression { + return &ast.Boolean{Token: p.curToken, Value: p.curTokenIs(token.TRUE)} +} + +func (p *Parser) parseGroupedExpression() ast.Expression { + p.nextToken() + + exp := p.parseExpression(LOWEST) + + if !p.expectPeek(token.RPAREN) { + return nil + } + + return exp +} + +func (p *Parser) parseIfExpression() ast.Expression { + expression := &ast.IfExpression{Token: p.curToken} + + if !p.expectPeek(token.LPAREN) { + return nil + } + + p.nextToken() + expression.Condition = p.parseExpression(LOWEST) + + if !p.expectPeek(token.RPAREN) { + return nil + } + + if !p.expectPeek(token.LBRACE) { + return nil + } + + expression.Consequence = p.parseBlockStatement() + + if p.peekTokenIs(token.ELSE) { + p.nextToken() + + if !p.expectPeek(token.LBRACE) { + return nil + } + + expression.Alternative = p.parseBlockStatement() + } + + return expression +} + +func (p *Parser) parseBlockStatement() *ast.BlockStatement { + block := &ast.BlockStatement{Token: p.curToken} + block.Statements = []ast.Statement{} + + p.nextToken() + + for !p.curTokenIs(token.RBRACE) && !p.curTokenIs(token.EOF) { + stmt := p.parseStatement() + if stmt != nil { + block.Statements = append(block.Statements, stmt) + } + p.nextToken() + } + + return block +} + +func (p *Parser) parseFunctionLiteral() ast.Expression { + lit := &ast.FunctionLiteral{Token: p.curToken} + + if !p.expectPeek(token.LPAREN) { + return nil + } + + lit.Parameters = p.parseFunctionParameters() + + if !p.expectPeek(token.LBRACE) { + return nil + } + + lit.Body = p.parseBlockStatement() + + return lit +} + +func (p *Parser) parseFunctionParameters() []*ast.Identifier { + identifiers := []*ast.Identifier{} + + if p.peekTokenIs(token.RPAREN) { + p.nextToken() + return identifiers + } + + p.nextToken() + + ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + identifiers = append(identifiers, ident) + + for p.peekTokenIs(token.COMMA) { + p.nextToken() + p.nextToken() + ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + identifiers = append(identifiers, ident) + } + + if !p.expectPeek(token.RPAREN) { + return nil + } + + return identifiers +} + +func (p *Parser) parseCallExpression(function ast.Expression) ast.Expression { + exp := &ast.CallExpression{Token: p.curToken, Function: function} + exp.Arguments = p.parseExpressionList(token.RPAREN) + return exp +} + +func (p *Parser) parseExpressionList(end token.TokenType) []ast.Expression { + list := []ast.Expression{} + + if p.peekTokenIs(end) { + p.nextToken() + return list + } + + p.nextToken() + list = append(list, p.parseExpression(LOWEST)) + + for p.peekTokenIs(token.COMMA) { + p.nextToken() + p.nextToken() + list = append(list, p.parseExpression(LOWEST)) + } + + if !p.expectPeek(end) { + return nil + } + + return list +} + +func (p *Parser) parseArrayLiteral() ast.Expression { + array := &ast.ArrayLiteral{Token: p.curToken} + + array.Elements = p.parseExpressionList(token.RBRACKET) + + return array +} + +func (p *Parser) parseIndexExpression(left ast.Expression) ast.Expression { + exp := &ast.IndexExpression{Token: p.curToken, Left: left} + + p.nextToken() + exp.Index = p.parseExpression(LOWEST) + + if !p.expectPeek(token.RBRACKET) { + return nil + } + + return exp +} + +func (p *Parser) parseHashLiteral() ast.Expression { + hash := &ast.HashLiteral{Token: p.curToken} + hash.Pairs = make(map[ast.Expression]ast.Expression) + + for !p.peekTokenIs(token.RBRACE) { + p.nextToken() + key := p.parseExpression(LOWEST) + + if !p.expectPeek(token.COLON) { + return nil + } + + p.nextToken() + value := p.parseExpression(LOWEST) + + hash.Pairs[key] = value + + if !p.peekTokenIs(token.RBRACE) && !p.expectPeek(token.COMMA) { + return nil + } + } + + if !p.expectPeek(token.RBRACE) { + return nil + } + + return hash +} + +func (p *Parser) registerPrefix(tokenType token.TokenType, fn prefixParseFn) { + p.prefixParseFns[tokenType] = fn +} + +func (p *Parser) registerInfix(tokenType token.TokenType, fn infixParseFn) { + p.infixParseFns[tokenType] = fn +} diff --git a/wcig_code_1_2/08/src/monkey/parser/parser_test.go b/wcig_code_1_2/08/src/monkey/parser/parser_test.go new file mode 100644 index 0000000..674487d --- /dev/null +++ b/wcig_code_1_2/08/src/monkey/parser/parser_test.go @@ -0,0 +1,1083 @@ +package parser + +import ( + "fmt" + "monkey/ast" + "monkey/lexer" + "testing" +) + +func TestLetStatements(t *testing.T) { + tests := []struct { + input string + expectedIdentifier string + expectedValue interface{} + }{ + {"let x = 5;", "x", 5}, + {"let y = true;", "y", true}, + {"let foobar = y;", "foobar", "y"}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain 1 statements. got=%d", + len(program.Statements)) + } + + stmt := program.Statements[0] + if !testLetStatement(t, stmt, tt.expectedIdentifier) { + return + } + + val := stmt.(*ast.LetStatement).Value + if !testLiteralExpression(t, val, tt.expectedValue) { + return + } + } +} + +func TestReturnStatements(t *testing.T) { + tests := []struct { + input string + expectedValue interface{} + }{ + {"return 5;", 5}, + {"return true;", true}, + {"return foobar;", "foobar"}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain 1 statements. got=%d", + len(program.Statements)) + } + + stmt := program.Statements[0] + returnStmt, ok := stmt.(*ast.ReturnStatement) + if !ok { + t.Fatalf("stmt not *ast.returnStatement. got=%T", stmt) + } + if returnStmt.TokenLiteral() != "return" { + t.Fatalf("returnStmt.TokenLiteral not 'return', got %q", + returnStmt.TokenLiteral()) + } + if testLiteralExpression(t, returnStmt.ReturnValue, tt.expectedValue) { + return + } + } +} + +func TestIdentifierExpression(t *testing.T) { + input := "foobar;" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program has not enough statements. got=%d", + len(program.Statements)) + } + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + ident, ok := stmt.Expression.(*ast.Identifier) + if !ok { + t.Fatalf("exp not *ast.Identifier. got=%T", stmt.Expression) + } + if ident.Value != "foobar" { + t.Errorf("ident.Value not %s. got=%s", "foobar", ident.Value) + } + if ident.TokenLiteral() != "foobar" { + t.Errorf("ident.TokenLiteral not %s. got=%s", "foobar", + ident.TokenLiteral()) + } +} + +func TestIntegerLiteralExpression(t *testing.T) { + input := "5;" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program has not enough statements. got=%d", + len(program.Statements)) + } + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + literal, ok := stmt.Expression.(*ast.IntegerLiteral) + if !ok { + t.Fatalf("exp not *ast.IntegerLiteral. got=%T", stmt.Expression) + } + if literal.Value != 5 { + t.Errorf("literal.Value not %d. got=%d", 5, literal.Value) + } + if literal.TokenLiteral() != "5" { + t.Errorf("literal.TokenLiteral not %s. got=%s", "5", + literal.TokenLiteral()) + } +} + +func TestParsingPrefixExpressions(t *testing.T) { + prefixTests := []struct { + input string + operator string + value interface{} + }{ + {"!5;", "!", 5}, + {"-15;", "-", 15}, + {"!foobar;", "!", "foobar"}, + {"-foobar;", "-", "foobar"}, + {"!true;", "!", true}, + {"!false;", "!", false}, + } + + for _, tt := range prefixTests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.PrefixExpression) + if !ok { + t.Fatalf("stmt is not ast.PrefixExpression. got=%T", stmt.Expression) + } + if exp.Operator != tt.operator { + t.Fatalf("exp.Operator is not '%s'. got=%s", + tt.operator, exp.Operator) + } + if !testLiteralExpression(t, exp.Right, tt.value) { + return + } + } +} + +func TestParsingInfixExpressions(t *testing.T) { + infixTests := []struct { + input string + leftValue interface{} + operator string + rightValue interface{} + }{ + {"5 + 5;", 5, "+", 5}, + {"5 - 5;", 5, "-", 5}, + {"5 * 5;", 5, "*", 5}, + {"5 / 5;", 5, "/", 5}, + {"5 > 5;", 5, ">", 5}, + {"5 < 5;", 5, "<", 5}, + {"5 == 5;", 5, "==", 5}, + {"5 != 5;", 5, "!=", 5}, + {"foobar + barfoo;", "foobar", "+", "barfoo"}, + {"foobar - barfoo;", "foobar", "-", "barfoo"}, + {"foobar * barfoo;", "foobar", "*", "barfoo"}, + {"foobar / barfoo;", "foobar", "/", "barfoo"}, + {"foobar > barfoo;", "foobar", ">", "barfoo"}, + {"foobar < barfoo;", "foobar", "<", "barfoo"}, + {"foobar == barfoo;", "foobar", "==", "barfoo"}, + {"foobar != barfoo;", "foobar", "!=", "barfoo"}, + {"true == true", true, "==", true}, + {"true != false", true, "!=", false}, + {"false == false", false, "==", false}, + } + + for _, tt := range infixTests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + if !testInfixExpression(t, stmt.Expression, tt.leftValue, + tt.operator, tt.rightValue) { + return + } + } +} + +func TestOperatorPrecedenceParsing(t *testing.T) { + tests := []struct { + input string + expected string + }{ + { + "-a * b", + "((-a) * b)", + }, + { + "!-a", + "(!(-a))", + }, + { + "a + b + c", + "((a + b) + c)", + }, + { + "a + b - c", + "((a + b) - c)", + }, + { + "a * b * c", + "((a * b) * c)", + }, + { + "a * b / c", + "((a * b) / c)", + }, + { + "a + b / c", + "(a + (b / c))", + }, + { + "a + b * c + d / e - f", + "(((a + (b * c)) + (d / e)) - f)", + }, + { + "3 + 4; -5 * 5", + "(3 + 4)((-5) * 5)", + }, + { + "5 > 4 == 3 < 4", + "((5 > 4) == (3 < 4))", + }, + { + "5 < 4 != 3 > 4", + "((5 < 4) != (3 > 4))", + }, + { + "3 + 4 * 5 == 3 * 1 + 4 * 5", + "((3 + (4 * 5)) == ((3 * 1) + (4 * 5)))", + }, + { + "true", + "true", + }, + { + "false", + "false", + }, + { + "3 > 5 == false", + "((3 > 5) == false)", + }, + { + "3 < 5 == true", + "((3 < 5) == true)", + }, + { + "1 + (2 + 3) + 4", + "((1 + (2 + 3)) + 4)", + }, + { + "(5 + 5) * 2", + "((5 + 5) * 2)", + }, + { + "2 / (5 + 5)", + "(2 / (5 + 5))", + }, + { + "(5 + 5) * 2 * (5 + 5)", + "(((5 + 5) * 2) * (5 + 5))", + }, + { + "-(5 + 5)", + "(-(5 + 5))", + }, + { + "!(true == true)", + "(!(true == true))", + }, + { + "a + add(b * c) + d", + "((a + add((b * c))) + d)", + }, + { + "add(a, b, 1, 2 * 3, 4 + 5, add(6, 7 * 8))", + "add(a, b, 1, (2 * 3), (4 + 5), add(6, (7 * 8)))", + }, + { + "add(a + b + c * d / f + g)", + "add((((a + b) + ((c * d) / f)) + g))", + }, + { + "a * [1, 2, 3, 4][b * c] * d", + "((a * ([1, 2, 3, 4][(b * c)])) * d)", + }, + { + "add(a * b[2], b[1], 2 * [1, 2][1])", + "add((a * (b[2])), (b[1]), (2 * ([1, 2][1])))", + }, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + actual := program.String() + if actual != tt.expected { + t.Errorf("expected=%q, got=%q", tt.expected, actual) + } + } +} + +func TestBooleanExpression(t *testing.T) { + tests := []struct { + input string + expectedBoolean bool + }{ + {"true;", true}, + {"false;", false}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program has not enough statements. got=%d", + len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + boolean, ok := stmt.Expression.(*ast.Boolean) + if !ok { + t.Fatalf("exp not *ast.Boolean. got=%T", stmt.Expression) + } + if boolean.Value != tt.expectedBoolean { + t.Errorf("boolean.Value not %t. got=%t", tt.expectedBoolean, + boolean.Value) + } + } +} + +func TestIfExpression(t *testing.T) { + input := `if (x < y) { x }` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.IfExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.IfExpression. got=%T", + stmt.Expression) + } + + if !testInfixExpression(t, exp.Condition, "x", "<", "y") { + return + } + + if len(exp.Consequence.Statements) != 1 { + t.Errorf("consequence is not 1 statements. got=%d\n", + len(exp.Consequence.Statements)) + } + + consequence, ok := exp.Consequence.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", + exp.Consequence.Statements[0]) + } + + if !testIdentifier(t, consequence.Expression, "x") { + return + } + + if exp.Alternative != nil { + t.Errorf("exp.Alternative.Statements was not nil. got=%+v", exp.Alternative) + } +} + +func TestIfElseExpression(t *testing.T) { + input := `if (x < y) { x } else { y }` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.IfExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.IfExpression. got=%T", stmt.Expression) + } + + if !testInfixExpression(t, exp.Condition, "x", "<", "y") { + return + } + + if len(exp.Consequence.Statements) != 1 { + t.Errorf("consequence is not 1 statements. got=%d\n", + len(exp.Consequence.Statements)) + } + + consequence, ok := exp.Consequence.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", + exp.Consequence.Statements[0]) + } + + if !testIdentifier(t, consequence.Expression, "x") { + return + } + + if len(exp.Alternative.Statements) != 1 { + t.Errorf("exp.Alternative.Statements does not contain 1 statements. got=%d\n", + len(exp.Alternative.Statements)) + } + + alternative, ok := exp.Alternative.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", + exp.Alternative.Statements[0]) + } + + if !testIdentifier(t, alternative.Expression, "y") { + return + } +} + +func TestFunctionLiteralParsing(t *testing.T) { + input := `fn(x, y) { x + y; }` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + function, ok := stmt.Expression.(*ast.FunctionLiteral) + if !ok { + t.Fatalf("stmt.Expression is not ast.FunctionLiteral. got=%T", + stmt.Expression) + } + + if len(function.Parameters) != 2 { + t.Fatalf("function literal parameters wrong. want 2, got=%d\n", + len(function.Parameters)) + } + + testLiteralExpression(t, function.Parameters[0], "x") + testLiteralExpression(t, function.Parameters[1], "y") + + if len(function.Body.Statements) != 1 { + t.Fatalf("function.Body.Statements has not 1 statements. got=%d\n", + len(function.Body.Statements)) + } + + bodyStmt, ok := function.Body.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("function body stmt is not ast.ExpressionStatement. got=%T", + function.Body.Statements[0]) + } + + testInfixExpression(t, bodyStmt.Expression, "x", "+", "y") +} + +func TestFunctionParameterParsing(t *testing.T) { + tests := []struct { + input string + expectedParams []string + }{ + {input: "fn() {};", expectedParams: []string{}}, + {input: "fn(x) {};", expectedParams: []string{"x"}}, + {input: "fn(x, y, z) {};", expectedParams: []string{"x", "y", "z"}}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + function := stmt.Expression.(*ast.FunctionLiteral) + + if len(function.Parameters) != len(tt.expectedParams) { + t.Errorf("length parameters wrong. want %d, got=%d\n", + len(tt.expectedParams), len(function.Parameters)) + } + + for i, ident := range tt.expectedParams { + testLiteralExpression(t, function.Parameters[i], ident) + } + } +} + +func TestCallExpressionParsing(t *testing.T) { + input := "add(1, 2 * 3, 4 + 5);" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("stmt is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.CallExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.CallExpression. got=%T", + stmt.Expression) + } + + if !testIdentifier(t, exp.Function, "add") { + return + } + + if len(exp.Arguments) != 3 { + t.Fatalf("wrong length of arguments. got=%d", len(exp.Arguments)) + } + + testLiteralExpression(t, exp.Arguments[0], 1) + testInfixExpression(t, exp.Arguments[1], 2, "*", 3) + testInfixExpression(t, exp.Arguments[2], 4, "+", 5) +} + +func TestCallExpressionParameterParsing(t *testing.T) { + tests := []struct { + input string + expectedIdent string + expectedArgs []string + }{ + { + input: "add();", + expectedIdent: "add", + expectedArgs: []string{}, + }, + { + input: "add(1);", + expectedIdent: "add", + expectedArgs: []string{"1"}, + }, + { + input: "add(1, 2 * 3, 4 + 5);", + expectedIdent: "add", + expectedArgs: []string{"1", "(2 * 3)", "(4 + 5)"}, + }, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + exp, ok := stmt.Expression.(*ast.CallExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.CallExpression. got=%T", + stmt.Expression) + } + + if !testIdentifier(t, exp.Function, tt.expectedIdent) { + return + } + + if len(exp.Arguments) != len(tt.expectedArgs) { + t.Fatalf("wrong number of arguments. want=%d, got=%d", + len(tt.expectedArgs), len(exp.Arguments)) + } + + for i, arg := range tt.expectedArgs { + if exp.Arguments[i].String() != arg { + t.Errorf("argument %d wrong. want=%q, got=%q", i, + arg, exp.Arguments[i].String()) + } + } + } +} + +func TestStringLiteralExpression(t *testing.T) { + input := `"hello world";` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + literal, ok := stmt.Expression.(*ast.StringLiteral) + if !ok { + t.Fatalf("exp not *ast.StringLiteral. got=%T", stmt.Expression) + } + + if literal.Value != "hello world" { + t.Errorf("literal.Value not %q. got=%q", "hello world", literal.Value) + } +} + +func TestParsingEmptyArrayLiterals(t *testing.T) { + input := "[]" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + array, ok := stmt.Expression.(*ast.ArrayLiteral) + if !ok { + t.Fatalf("exp not ast.ArrayLiteral. got=%T", stmt.Expression) + } + + if len(array.Elements) != 0 { + t.Errorf("len(array.Elements) not 0. got=%d", len(array.Elements)) + } +} + +func TestParsingArrayLiterals(t *testing.T) { + input := "[1, 2 * 2, 3 + 3]" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + array, ok := stmt.Expression.(*ast.ArrayLiteral) + if !ok { + t.Fatalf("exp not ast.ArrayLiteral. got=%T", stmt.Expression) + } + + if len(array.Elements) != 3 { + t.Fatalf("len(array.Elements) not 3. got=%d", len(array.Elements)) + } + + testIntegerLiteral(t, array.Elements[0], 1) + testInfixExpression(t, array.Elements[1], 2, "*", 2) + testInfixExpression(t, array.Elements[2], 3, "+", 3) +} + +func TestParsingIndexExpressions(t *testing.T) { + input := "myArray[1 + 1]" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + indexExp, ok := stmt.Expression.(*ast.IndexExpression) + if !ok { + t.Fatalf("exp not *ast.IndexExpression. got=%T", stmt.Expression) + } + + if !testIdentifier(t, indexExp.Left, "myArray") { + return + } + + if !testInfixExpression(t, indexExp.Index, 1, "+", 1) { + return + } +} + +func TestParsingEmptyHashLiteral(t *testing.T) { + input := "{}" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + if len(hash.Pairs) != 0 { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } +} + +func TestParsingHashLiteralsStringKeys(t *testing.T) { + input := `{"one": 1, "two": 2, "three": 3}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + expected := map[string]int64{ + "one": 1, + "two": 2, + "three": 3, + } + + if len(hash.Pairs) != len(expected) { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } + + for key, value := range hash.Pairs { + literal, ok := key.(*ast.StringLiteral) + if !ok { + t.Errorf("key is not ast.StringLiteral. got=%T", key) + continue + } + + expectedValue := expected[literal.String()] + testIntegerLiteral(t, value, expectedValue) + } +} + +func TestParsingHashLiteralsBooleanKeys(t *testing.T) { + input := `{true: 1, false: 2}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + expected := map[string]int64{ + "true": 1, + "false": 2, + } + + if len(hash.Pairs) != len(expected) { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } + + for key, value := range hash.Pairs { + boolean, ok := key.(*ast.Boolean) + if !ok { + t.Errorf("key is not ast.BooleanLiteral. got=%T", key) + continue + } + + expectedValue := expected[boolean.String()] + testIntegerLiteral(t, value, expectedValue) + } +} + +func TestParsingHashLiteralsIntegerKeys(t *testing.T) { + input := `{1: 1, 2: 2, 3: 3}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + expected := map[string]int64{ + "1": 1, + "2": 2, + "3": 3, + } + + if len(hash.Pairs) != len(expected) { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } + + for key, value := range hash.Pairs { + integer, ok := key.(*ast.IntegerLiteral) + if !ok { + t.Errorf("key is not ast.IntegerLiteral. got=%T", key) + continue + } + + expectedValue := expected[integer.String()] + + testIntegerLiteral(t, value, expectedValue) + } +} + +func TestParsingHashLiteralsWithExpressions(t *testing.T) { + input := `{"one": 0 + 1, "two": 10 - 8, "three": 15 / 5}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + if len(hash.Pairs) != 3 { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } + + tests := map[string]func(ast.Expression){ + "one": func(e ast.Expression) { + testInfixExpression(t, e, 0, "+", 1) + }, + "two": func(e ast.Expression) { + testInfixExpression(t, e, 10, "-", 8) + }, + "three": func(e ast.Expression) { + testInfixExpression(t, e, 15, "/", 5) + }, + } + + for key, value := range hash.Pairs { + literal, ok := key.(*ast.StringLiteral) + if !ok { + t.Errorf("key is not ast.StringLiteral. got=%T", key) + continue + } + + testFunc, ok := tests[literal.String()] + if !ok { + t.Errorf("No test function for key %q found", literal.String()) + continue + } + + testFunc(value) + } +} + +func testLetStatement(t *testing.T, s ast.Statement, name string) bool { + if s.TokenLiteral() != "let" { + t.Errorf("s.TokenLiteral not 'let'. got=%q", s.TokenLiteral()) + return false + } + + letStmt, ok := s.(*ast.LetStatement) + if !ok { + t.Errorf("s not *ast.LetStatement. got=%T", s) + return false + } + + if letStmt.Name.Value != name { + t.Errorf("letStmt.Name.Value not '%s'. got=%s", name, letStmt.Name.Value) + return false + } + + if letStmt.Name.TokenLiteral() != name { + t.Errorf("letStmt.Name.TokenLiteral() not '%s'. got=%s", + name, letStmt.Name.TokenLiteral()) + return false + } + + return true +} + +func testInfixExpression(t *testing.T, exp ast.Expression, left interface{}, + operator string, right interface{}) bool { + + opExp, ok := exp.(*ast.InfixExpression) + if !ok { + t.Errorf("exp is not ast.InfixExpression. got=%T(%s)", exp, exp) + return false + } + + if !testLiteralExpression(t, opExp.Left, left) { + return false + } + + if opExp.Operator != operator { + t.Errorf("exp.Operator is not '%s'. got=%q", operator, opExp.Operator) + return false + } + + if !testLiteralExpression(t, opExp.Right, right) { + return false + } + + return true +} + +func testLiteralExpression( + t *testing.T, + exp ast.Expression, + expected interface{}, +) bool { + switch v := expected.(type) { + case int: + return testIntegerLiteral(t, exp, int64(v)) + case int64: + return testIntegerLiteral(t, exp, v) + case string: + return testIdentifier(t, exp, v) + case bool: + return testBooleanLiteral(t, exp, v) + } + t.Errorf("type of exp not handled. got=%T", exp) + return false +} + +func testIntegerLiteral(t *testing.T, il ast.Expression, value int64) bool { + integ, ok := il.(*ast.IntegerLiteral) + if !ok { + t.Errorf("il not *ast.IntegerLiteral. got=%T", il) + return false + } + + if integ.Value != value { + t.Errorf("integ.Value not %d. got=%d", value, integ.Value) + return false + } + + if integ.TokenLiteral() != fmt.Sprintf("%d", value) { + t.Errorf("integ.TokenLiteral not %d. got=%s", value, + integ.TokenLiteral()) + return false + } + + return true +} + +func testIdentifier(t *testing.T, exp ast.Expression, value string) bool { + ident, ok := exp.(*ast.Identifier) + if !ok { + t.Errorf("exp not *ast.Identifier. got=%T", exp) + return false + } + + if ident.Value != value { + t.Errorf("ident.Value not %s. got=%s", value, ident.Value) + return false + } + + if ident.TokenLiteral() != value { + t.Errorf("ident.TokenLiteral not %s. got=%s", value, + ident.TokenLiteral()) + return false + } + + return true +} + +func testBooleanLiteral(t *testing.T, exp ast.Expression, value bool) bool { + bo, ok := exp.(*ast.Boolean) + if !ok { + t.Errorf("exp not *ast.Boolean. got=%T", exp) + return false + } + + if bo.Value != value { + t.Errorf("bo.Value not %t. got=%t", value, bo.Value) + return false + } + + if bo.TokenLiteral() != fmt.Sprintf("%t", value) { + t.Errorf("bo.TokenLiteral not %t. got=%s", + value, bo.TokenLiteral()) + return false + } + + return true +} + +func checkParserErrors(t *testing.T, p *Parser) { + errors := p.Errors() + if len(errors) == 0 { + return + } + + t.Errorf("parser has %d errors", len(errors)) + for _, msg := range errors { + t.Errorf("parser error: %q", msg) + } + t.FailNow() +} diff --git a/wcig_code_1_2/08/src/monkey/parser/parser_tracing.go b/wcig_code_1_2/08/src/monkey/parser/parser_tracing.go new file mode 100644 index 0000000..5fc569b --- /dev/null +++ b/wcig_code_1_2/08/src/monkey/parser/parser_tracing.go @@ -0,0 +1,32 @@ +package parser + +import ( + "fmt" + "strings" +) + +var traceLevel int = 0 + +const traceIdentPlaceholder string = "\t" + +func identLevel() string { + return strings.Repeat(traceIdentPlaceholder, traceLevel-1) +} + +func tracePrint(fs string) { + fmt.Printf("%s%s\n", identLevel(), fs) +} + +func incIdent() { traceLevel = traceLevel + 1 } +func decIdent() { traceLevel = traceLevel - 1 } + +func trace(msg string) string { + incIdent() + tracePrint("BEGIN " + msg) + return msg +} + +func untrace(msg string) { + tracePrint("END " + msg) + decIdent() +} diff --git a/wcig_code_1_2/08/src/monkey/repl/repl.go b/wcig_code_1_2/08/src/monkey/repl/repl.go new file mode 100644 index 0000000..dac5563 --- /dev/null +++ b/wcig_code_1_2/08/src/monkey/repl/repl.go @@ -0,0 +1,87 @@ +package repl + +import ( + "bufio" + "fmt" + "io" + "monkey/compiler" + "monkey/lexer" + "monkey/object" + "monkey/parser" + "monkey/vm" +) + +const PROMPT = ">> " + +func Start(in io.Reader, out io.Writer) { + scanner := bufio.NewScanner(in) + + constants := []object.Object{} + globals := make([]object.Object, vm.GlobalsSize) + + symbolTable := compiler.NewSymbolTable() + for i, v := range object.Builtins { + symbolTable.DefineBuiltin(i, v.Name) + } + + for { + fmt.Fprintf(out, PROMPT) + scanned := scanner.Scan() + if !scanned { + return + } + + line := scanner.Text() + l := lexer.New(line) + p := parser.New(l) + + program := p.ParseProgram() + if len(p.Errors()) != 0 { + printParserErrors(out, p.Errors()) + continue + } + + comp := compiler.NewWithState(symbolTable, constants) + err := comp.Compile(program) + if err != nil { + fmt.Fprintf(out, "Woops! Compilation failed:\n %s\n", err) + continue + } + + code := comp.Bytecode() + constants = code.Constants + + machine := vm.NewWithGlobalsStore(code, globals) + err = machine.Run() + if err != nil { + fmt.Fprintf(out, "Woops! Executing bytecode failed:\n %s\n", err) + continue + } + + lastPopped := machine.LastPoppedStackElem() + io.WriteString(out, lastPopped.Inspect()) + io.WriteString(out, "\n") + } +} + +const MONKEY_FACE = ` __,__ + .--. .-" "-. .--. + / .. \/ .-. .-. \/ .. \ + | | '| / Y \ |' | | + | \ \ \ 0 | 0 / / / | + \ '- ,\.-"""""""-./, -' / + ''-' /_ ^ ^ _\ '-'' + | \._ _./ | + \ \ '~' / / + '._ '-=-' _.' + '-----' +` + +func printParserErrors(out io.Writer, errors []string) { + io.WriteString(out, MONKEY_FACE) + io.WriteString(out, "Woops! We ran into some monkey business here!\n") + io.WriteString(out, " parser errors:\n") + for _, msg := range errors { + io.WriteString(out, "\t"+msg+"\n") + } +} diff --git a/wcig_code_1_2/08/src/monkey/token/token.go b/wcig_code_1_2/08/src/monkey/token/token.go new file mode 100644 index 0000000..3d2d2f7 --- /dev/null +++ b/wcig_code_1_2/08/src/monkey/token/token.go @@ -0,0 +1,70 @@ +package token + +type TokenType string + +const ( + ILLEGAL = "ILLEGAL" + EOF = "EOF" + + // Identifiers + literals + IDENT = "IDENT" // add, foobar, x, y, ... + INT = "INT" // 1343456 + STRING = "STRING" // "foobar" + + // Operators + ASSIGN = "=" + PLUS = "+" + MINUS = "-" + BANG = "!" + ASTERISK = "*" + SLASH = "/" + + LT = "<" + GT = ">" + + EQ = "==" + NOT_EQ = "!=" + + // Delimiters + COMMA = "," + SEMICOLON = ";" + COLON = ":" + + LPAREN = "(" + RPAREN = ")" + LBRACE = "{" + RBRACE = "}" + LBRACKET = "[" + RBRACKET = "]" + + // Keywords + FUNCTION = "FUNCTION" + LET = "LET" + TRUE = "TRUE" + FALSE = "FALSE" + IF = "IF" + ELSE = "ELSE" + RETURN = "RETURN" +) + +type Token struct { + Type TokenType + Literal string +} + +var keywords = map[string]TokenType{ + "fn": FUNCTION, + "let": LET, + "true": TRUE, + "false": FALSE, + "if": IF, + "else": ELSE, + "return": RETURN, +} + +func LookupIdent(ident string) TokenType { + if tok, ok := keywords[ident]; ok { + return tok + } + return IDENT +} diff --git a/wcig_code_1_2/08/src/monkey/vm/frame.go b/wcig_code_1_2/08/src/monkey/vm/frame.go new file mode 100644 index 0000000..0fa415b --- /dev/null +++ b/wcig_code_1_2/08/src/monkey/vm/frame.go @@ -0,0 +1,26 @@ +package vm + +import ( + "monkey/code" + "monkey/object" +) + +type Frame struct { + fn *object.CompiledFunction + ip int + basePointer int +} + +func NewFrame(fn *object.CompiledFunction, basePointer int) *Frame { + f := &Frame{ + fn: fn, + ip: -1, + basePointer: basePointer, + } + + return f +} + +func (f *Frame) Instructions() code.Instructions { + return f.fn.Instructions +} diff --git a/wcig_code_1_2/08/src/monkey/vm/vm.go b/wcig_code_1_2/08/src/monkey/vm/vm.go new file mode 100644 index 0000000..0dc5ec1 --- /dev/null +++ b/wcig_code_1_2/08/src/monkey/vm/vm.go @@ -0,0 +1,536 @@ +package vm + +import ( + "fmt" + "monkey/code" + "monkey/compiler" + "monkey/object" +) + +const StackSize = 2048 +const GlobalsSize = 65536 +const MaxFrames = 1024 + +var True = &object.Boolean{Value: true} +var False = &object.Boolean{Value: false} +var Null = &object.Null{} + +type VM struct { + constants []object.Object + + stack []object.Object + sp int // Always points to the next value. Top of stack is stack[sp-1] + + globals []object.Object + + frames []*Frame + framesIndex int +} + +func New(bytecode *compiler.Bytecode) *VM { + mainFn := &object.CompiledFunction{Instructions: bytecode.Instructions} + mainFrame := NewFrame(mainFn, 0) + + frames := make([]*Frame, MaxFrames) + frames[0] = mainFrame + + return &VM{ + constants: bytecode.Constants, + + stack: make([]object.Object, StackSize), + sp: 0, + + globals: make([]object.Object, GlobalsSize), + + frames: frames, + framesIndex: 1, + } +} + +func NewWithGlobalsStore(bytecode *compiler.Bytecode, s []object.Object) *VM { + vm := New(bytecode) + vm.globals = s + return vm +} + +func (vm *VM) LastPoppedStackElem() object.Object { + return vm.stack[vm.sp] +} + +func (vm *VM) Run() error { + var ip int + var ins code.Instructions + var op code.Opcode + + for vm.currentFrame().ip < len(vm.currentFrame().Instructions())-1 { + vm.currentFrame().ip++ + + ip = vm.currentFrame().ip + ins = vm.currentFrame().Instructions() + op = code.Opcode(ins[ip]) + + switch op { + case code.OpConstant: + constIndex := code.ReadUint16(ins[ip+1:]) + vm.currentFrame().ip += 2 + + err := vm.push(vm.constants[constIndex]) + if err != nil { + return err + } + + case code.OpPop: + vm.pop() + + case code.OpAdd, code.OpSub, code.OpMul, code.OpDiv: + err := vm.executeBinaryOperation(op) + if err != nil { + return err + } + + case code.OpTrue: + err := vm.push(True) + if err != nil { + return err + } + + case code.OpFalse: + err := vm.push(False) + if err != nil { + return err + } + + case code.OpEqual, code.OpNotEqual, code.OpGreaterThan: + err := vm.executeComparison(op) + if err != nil { + return err + } + + case code.OpBang: + err := vm.executeBangOperator() + if err != nil { + return err + } + + case code.OpMinus: + err := vm.executeMinusOperator() + if err != nil { + return err + } + + case code.OpJump: + pos := int(code.ReadUint16(ins[ip+1:])) + vm.currentFrame().ip = pos - 1 + + case code.OpJumpNotTruthy: + pos := int(code.ReadUint16(ins[ip+1:])) + vm.currentFrame().ip += 2 + + condition := vm.pop() + if !isTruthy(condition) { + vm.currentFrame().ip = pos - 1 + } + + case code.OpNull: + err := vm.push(Null) + if err != nil { + return err + } + + case code.OpSetGlobal: + globalIndex := code.ReadUint16(ins[ip+1:]) + vm.currentFrame().ip += 2 + + vm.globals[globalIndex] = vm.pop() + + case code.OpGetGlobal: + globalIndex := code.ReadUint16(ins[ip+1:]) + vm.currentFrame().ip += 2 + + err := vm.push(vm.globals[globalIndex]) + if err != nil { + return err + } + + case code.OpArray: + numElements := int(code.ReadUint16(ins[ip+1:])) + vm.currentFrame().ip += 2 + + array := vm.buildArray(vm.sp-numElements, vm.sp) + vm.sp = vm.sp - numElements + + err := vm.push(array) + if err != nil { + return err + } + + case code.OpHash: + numElements := int(code.ReadUint16(ins[ip+1:])) + vm.currentFrame().ip += 2 + + hash, err := vm.buildHash(vm.sp-numElements, vm.sp) + if err != nil { + return err + } + vm.sp = vm.sp - numElements + + err = vm.push(hash) + if err != nil { + return err + } + + case code.OpIndex: + index := vm.pop() + left := vm.pop() + + err := vm.executeIndexExpression(left, index) + if err != nil { + return err + } + + case code.OpCall: + numArgs := code.ReadUint8(ins[ip+1:]) + vm.currentFrame().ip += 1 + + err := vm.executeCall(int(numArgs)) + if err != nil { + return err + } + + case code.OpReturnValue: + returnValue := vm.pop() + + frame := vm.popFrame() + vm.sp = frame.basePointer - 1 + + err := vm.push(returnValue) + if err != nil { + return err + } + + case code.OpReturn: + frame := vm.popFrame() + vm.sp = frame.basePointer - 1 + + err := vm.push(Null) + if err != nil { + return err + } + + case code.OpSetLocal: + localIndex := code.ReadUint8(ins[ip+1:]) + vm.currentFrame().ip += 1 + + frame := vm.currentFrame() + + vm.stack[frame.basePointer+int(localIndex)] = vm.pop() + + case code.OpGetLocal: + localIndex := code.ReadUint8(ins[ip+1:]) + vm.currentFrame().ip += 1 + + frame := vm.currentFrame() + + err := vm.push(vm.stack[frame.basePointer+int(localIndex)]) + if err != nil { + return err + } + + case code.OpGetBuiltin: + builtinIndex := code.ReadUint8(ins[ip+1:]) + vm.currentFrame().ip += 1 + + definition := object.Builtins[builtinIndex] + + err := vm.push(definition.Builtin) + if err != nil { + return err + } + } + } + + return nil +} + +func (vm *VM) push(o object.Object) error { + if vm.sp >= StackSize { + return fmt.Errorf("stack overflow") + } + + vm.stack[vm.sp] = o + vm.sp++ + + return nil +} + +func (vm *VM) pop() object.Object { + o := vm.stack[vm.sp-1] + vm.sp-- + return o +} + +func (vm *VM) executeBinaryOperation(op code.Opcode) error { + right := vm.pop() + left := vm.pop() + + leftType := left.Type() + rightType := right.Type() + + switch { + case leftType == object.INTEGER_OBJ && rightType == object.INTEGER_OBJ: + return vm.executeBinaryIntegerOperation(op, left, right) + case leftType == object.STRING_OBJ && rightType == object.STRING_OBJ: + return vm.executeBinaryStringOperation(op, left, right) + default: + return fmt.Errorf("unsupported types for binary operation: %s %s", + leftType, rightType) + } +} + +func (vm *VM) executeBinaryIntegerOperation( + op code.Opcode, + left, right object.Object, +) error { + leftValue := left.(*object.Integer).Value + rightValue := right.(*object.Integer).Value + + var result int64 + + switch op { + case code.OpAdd: + result = leftValue + rightValue + case code.OpSub: + result = leftValue - rightValue + case code.OpMul: + result = leftValue * rightValue + case code.OpDiv: + result = leftValue / rightValue + default: + return fmt.Errorf("unknown integer operator: %d", op) + } + + return vm.push(&object.Integer{Value: result}) +} + +func (vm *VM) executeComparison(op code.Opcode) error { + right := vm.pop() + left := vm.pop() + + if left.Type() == object.INTEGER_OBJ && right.Type() == object.INTEGER_OBJ { + return vm.executeIntegerComparison(op, left, right) + } + + switch op { + case code.OpEqual: + return vm.push(nativeBoolToBooleanObject(right == left)) + case code.OpNotEqual: + return vm.push(nativeBoolToBooleanObject(right != left)) + default: + return fmt.Errorf("unknown operator: %d (%s %s)", + op, left.Type(), right.Type()) + } +} + +func (vm *VM) executeIntegerComparison( + op code.Opcode, + left, right object.Object, +) error { + leftValue := left.(*object.Integer).Value + rightValue := right.(*object.Integer).Value + + switch op { + case code.OpEqual: + return vm.push(nativeBoolToBooleanObject(rightValue == leftValue)) + case code.OpNotEqual: + return vm.push(nativeBoolToBooleanObject(rightValue != leftValue)) + case code.OpGreaterThan: + return vm.push(nativeBoolToBooleanObject(leftValue > rightValue)) + default: + return fmt.Errorf("unknown operator: %d", op) + } +} + +func (vm *VM) executeBangOperator() error { + operand := vm.pop() + + switch operand { + case True: + return vm.push(False) + case False: + return vm.push(True) + case Null: + return vm.push(True) + default: + return vm.push(False) + } +} + +func (vm *VM) executeMinusOperator() error { + operand := vm.pop() + + if operand.Type() != object.INTEGER_OBJ { + return fmt.Errorf("unsupported type for negation: %s", operand.Type()) + } + + value := operand.(*object.Integer).Value + return vm.push(&object.Integer{Value: -value}) +} + +func (vm *VM) executeBinaryStringOperation( + op code.Opcode, + left, right object.Object, +) error { + if op != code.OpAdd { + return fmt.Errorf("unknown string operator: %d", op) + } + + leftValue := left.(*object.String).Value + rightValue := right.(*object.String).Value + + return vm.push(&object.String{Value: leftValue + rightValue}) +} + +func (vm *VM) buildArray(startIndex, endIndex int) object.Object { + elements := make([]object.Object, endIndex-startIndex) + + for i := startIndex; i < endIndex; i++ { + elements[i-startIndex] = vm.stack[i] + } + + return &object.Array{Elements: elements} +} + +func (vm *VM) buildHash(startIndex, endIndex int) (object.Object, error) { + hashedPairs := make(map[object.HashKey]object.HashPair) + + for i := startIndex; i < endIndex; i += 2 { + key := vm.stack[i] + value := vm.stack[i+1] + + pair := object.HashPair{Key: key, Value: value} + + hashKey, ok := key.(object.Hashable) + if !ok { + return nil, fmt.Errorf("unusable as hash key: %s", key.Type()) + } + + hashedPairs[hashKey.HashKey()] = pair + } + + return &object.Hash{Pairs: hashedPairs}, nil +} + +func (vm *VM) executeIndexExpression(left, index object.Object) error { + switch { + case left.Type() == object.ARRAY_OBJ && index.Type() == object.INTEGER_OBJ: + return vm.executeArrayIndex(left, index) + case left.Type() == object.HASH_OBJ: + return vm.executeHashIndex(left, index) + default: + return fmt.Errorf("index operator not supported: %s", left.Type()) + } +} + +func (vm *VM) executeArrayIndex(array, index object.Object) error { + arrayObject := array.(*object.Array) + i := index.(*object.Integer).Value + max := int64(len(arrayObject.Elements) - 1) + + if i < 0 || i > max { + return vm.push(Null) + } + + return vm.push(arrayObject.Elements[i]) +} + +func (vm *VM) executeHashIndex(hash, index object.Object) error { + hashObject := hash.(*object.Hash) + + key, ok := index.(object.Hashable) + if !ok { + return fmt.Errorf("unusable as hash key: %s", index.Type()) + } + + pair, ok := hashObject.Pairs[key.HashKey()] + if !ok { + return vm.push(Null) + } + + return vm.push(pair.Value) +} + +func (vm *VM) currentFrame() *Frame { + return vm.frames[vm.framesIndex-1] +} + +func (vm *VM) pushFrame(f *Frame) { + vm.frames[vm.framesIndex] = f + vm.framesIndex++ +} + +func (vm *VM) popFrame() *Frame { + vm.framesIndex-- + return vm.frames[vm.framesIndex] +} + +func (vm *VM) executeCall(numArgs int) error { + callee := vm.stack[vm.sp-1-numArgs] + switch callee := callee.(type) { + case *object.CompiledFunction: + return vm.callFunction(callee, numArgs) + case *object.Builtin: + return vm.callBuiltin(callee, numArgs) + default: + return fmt.Errorf("calling non-function and non-built-in") + } +} + +func (vm *VM) callFunction(fn *object.CompiledFunction, numArgs int) error { + if numArgs != fn.NumParameters { + return fmt.Errorf("wrong number of arguments: want=%d, got=%d", + fn.NumParameters, numArgs) + } + + frame := NewFrame(fn, vm.sp-numArgs) + vm.pushFrame(frame) + + vm.sp = frame.basePointer + fn.NumLocals + + return nil +} + +func (vm *VM) callBuiltin(builtin *object.Builtin, numArgs int) error { + args := vm.stack[vm.sp-numArgs : vm.sp] + + result := builtin.Fn(args...) + vm.sp = vm.sp - numArgs - 1 + + if result != nil { + vm.push(result) + } else { + vm.push(Null) + } + + return nil +} + +func nativeBoolToBooleanObject(input bool) *object.Boolean { + if input { + return True + } + return False +} + +func isTruthy(obj object.Object) bool { + switch obj := obj.(type) { + + case *object.Boolean: + return obj.Value + + case *object.Null: + return false + + default: + return true + } +} diff --git a/wcig_code_1_2/08/src/monkey/vm/vm_test.go b/wcig_code_1_2/08/src/monkey/vm/vm_test.go new file mode 100644 index 0000000..0685ffa --- /dev/null +++ b/wcig_code_1_2/08/src/monkey/vm/vm_test.go @@ -0,0 +1,630 @@ +package vm + +import ( + "fmt" + "monkey/ast" + "monkey/compiler" + "monkey/lexer" + "monkey/object" + "monkey/parser" + "testing" +) + +func TestIntegerArithmetic(t *testing.T) { + tests := []vmTestCase{ + {"1", 1}, + {"2", 2}, + {"1 + 2", 3}, + {"1 - 2", -1}, + {"1 * 2", 2}, + {"4 / 2", 2}, + {"50 / 2 * 2 + 10 - 5", 55}, + {"5 * (2 + 10)", 60}, + {"5 + 5 + 5 + 5 - 10", 10}, + {"2 * 2 * 2 * 2 * 2", 32}, + {"5 * 2 + 10", 20}, + {"5 + 2 * 10", 25}, + {"5 * (2 + 10)", 60}, + {"-5", -5}, + {"-10", -10}, + {"-50 + 100 + -50", 0}, + {"(5 + 10 * 2 + 15 / 3) * 2 + -10", 50}, + } + + runVmTests(t, tests) +} + +func TestBooleanExpressions(t *testing.T) { + tests := []vmTestCase{ + {"true", true}, + {"false", false}, + {"1 < 2", true}, + {"1 > 2", false}, + {"1 < 1", false}, + {"1 > 1", false}, + {"1 == 1", true}, + {"1 != 1", false}, + {"1 == 2", false}, + {"1 != 2", true}, + {"true == true", true}, + {"false == false", true}, + {"true == false", false}, + {"true != false", true}, + {"false != true", true}, + {"(1 < 2) == true", true}, + {"(1 < 2) == false", false}, + {"(1 > 2) == true", false}, + {"(1 > 2) == false", true}, + {"!true", false}, + {"!false", true}, + {"!5", false}, + {"!!true", true}, + {"!!false", false}, + {"!!5", true}, + {"!(if (false) { 5; })", true}, + } + + runVmTests(t, tests) +} + +func TestConditionals(t *testing.T) { + tests := []vmTestCase{ + {"if (true) { 10 }", 10}, + {"if (true) { 10 } else { 20 }", 10}, + {"if (false) { 10 } else { 20 } ", 20}, + {"if (1) { 10 }", 10}, + {"if (1 < 2) { 10 }", 10}, + {"if (1 < 2) { 10 } else { 20 }", 10}, + {"if (1 > 2) { 10 } else { 20 }", 20}, + {"if (1 > 2) { 10 }", Null}, + {"if (false) { 10 }", Null}, + {"if ((if (false) { 10 })) { 10 } else { 20 }", 20}, + } + + runVmTests(t, tests) +} + +func TestGlobalLetStatements(t *testing.T) { + tests := []vmTestCase{ + {"let one = 1; one", 1}, + {"let one = 1; let two = 2; one + two", 3}, + {"let one = 1; let two = one + one; one + two", 3}, + } + + runVmTests(t, tests) +} + +func TestStringExpressions(t *testing.T) { + tests := []vmTestCase{ + {`"monkey"`, "monkey"}, + {`"mon" + "key"`, "monkey"}, + {`"mon" + "key" + "banana"`, "monkeybanana"}, + } + + runVmTests(t, tests) +} + +func TestArrayLiterals(t *testing.T) { + tests := []vmTestCase{ + {"[]", []int{}}, + {"[1, 2, 3]", []int{1, 2, 3}}, + {"[1 + 2, 3 * 4, 5 + 6]", []int{3, 12, 11}}, + } + + runVmTests(t, tests) +} + +func TestHashLiterals(t *testing.T) { + tests := []vmTestCase{ + { + "{}", map[object.HashKey]int64{}, + }, + { + "{1: 2, 2: 3}", + map[object.HashKey]int64{ + (&object.Integer{Value: 1}).HashKey(): 2, + (&object.Integer{Value: 2}).HashKey(): 3, + }, + }, + { + "{1 + 1: 2 * 2, 3 + 3: 4 * 4}", + map[object.HashKey]int64{ + (&object.Integer{Value: 2}).HashKey(): 4, + (&object.Integer{Value: 6}).HashKey(): 16, + }, + }, + } + + runVmTests(t, tests) +} + +func TestIndexExpressions(t *testing.T) { + tests := []vmTestCase{ + {"[1, 2, 3][1]", 2}, + {"[1, 2, 3][0 + 2]", 3}, + {"[[1, 1, 1]][0][0]", 1}, + {"[][0]", Null}, + {"[1, 2, 3][99]", Null}, + {"[1][-1]", Null}, + {"{1: 1, 2: 2}[1]", 1}, + {"{1: 1, 2: 2}[2]", 2}, + {"{1: 1}[0]", Null}, + {"{}[0]", Null}, + } + + runVmTests(t, tests) +} + +func TestCallingFunctionsWithoutArguments(t *testing.T) { + tests := []vmTestCase{ + { + input: ` + let fivePlusTen = fn() { 5 + 10; }; + fivePlusTen(); + `, + expected: 15, + }, + { + input: ` + let one = fn() { 1; }; + let two = fn() { 2; }; + one() + two() + `, + expected: 3, + }, + { + input: ` + let a = fn() { 1 }; + let b = fn() { a() + 1 }; + let c = fn() { b() + 1 }; + c(); + `, + expected: 3, + }, + } + + runVmTests(t, tests) +} + +func TestFunctionsWithReturnStatement(t *testing.T) { + tests := []vmTestCase{ + { + input: ` + let earlyExit = fn() { return 99; 100; }; + earlyExit(); + `, + expected: 99, + }, + { + input: ` + let earlyExit = fn() { return 99; return 100; }; + earlyExit(); + `, + expected: 99, + }, + } + + runVmTests(t, tests) +} + +func TestFunctionsWithoutReturnValue(t *testing.T) { + tests := []vmTestCase{ + { + input: ` + let noReturn = fn() { }; + noReturn(); + `, + expected: Null, + }, + { + input: ` + let noReturn = fn() { }; + let noReturnTwo = fn() { noReturn(); }; + noReturn(); + noReturnTwo(); + `, + expected: Null, + }, + } + + runVmTests(t, tests) +} + +func TestFirstClassFunctions(t *testing.T) { + tests := []vmTestCase{ + { + input: ` + let returnsOne = fn() { 1; }; + let returnsOneReturner = fn() { returnsOne; }; + returnsOneReturner()(); + `, + expected: 1, + }, + { + input: ` + let returnsOneReturner = fn() { + let returnsOne = fn() { 1; }; + returnsOne; + }; + returnsOneReturner()(); + `, + expected: 1, + }, + } + + runVmTests(t, tests) +} + +func TestCallingFunctionsWithBindings(t *testing.T) { + tests := []vmTestCase{ + { + input: ` + let one = fn() { let one = 1; one }; + one(); + `, + expected: 1, + }, + { + input: ` + let oneAndTwo = fn() { let one = 1; let two = 2; one + two; }; + oneAndTwo(); + `, + expected: 3, + }, + { + input: ` + let oneAndTwo = fn() { let one = 1; let two = 2; one + two; }; + let threeAndFour = fn() { let three = 3; let four = 4; three + four; }; + oneAndTwo() + threeAndFour(); + `, + expected: 10, + }, + { + input: ` + let firstFoobar = fn() { let foobar = 50; foobar; }; + let secondFoobar = fn() { let foobar = 100; foobar; }; + firstFoobar() + secondFoobar(); + `, + expected: 150, + }, + { + input: ` + let globalSeed = 50; + let minusOne = fn() { + let num = 1; + globalSeed - num; + } + let minusTwo = fn() { + let num = 2; + globalSeed - num; + } + minusOne() + minusTwo(); + `, + expected: 97, + }, + } + + runVmTests(t, tests) +} + +func TestCallingFunctionsWithArgumentsAndBindings(t *testing.T) { + tests := []vmTestCase{ + { + input: ` + let identity = fn(a) { a; }; + identity(4); + `, + expected: 4, + }, + { + input: ` + let sum = fn(a, b) { a + b; }; + sum(1, 2); + `, + expected: 3, + }, + { + input: ` + let sum = fn(a, b) { + let c = a + b; + c; + }; + sum(1, 2); + `, + expected: 3, + }, + { + input: ` + let sum = fn(a, b) { + let c = a + b; + c; + }; + sum(1, 2) + sum(3, 4);`, + expected: 10, + }, + { + input: ` + let sum = fn(a, b) { + let c = a + b; + c; + }; + let outer = fn() { + sum(1, 2) + sum(3, 4); + }; + outer(); + `, + expected: 10, + }, + { + input: ` + let globalNum = 10; + + let sum = fn(a, b) { + let c = a + b; + c + globalNum; + }; + + let outer = fn() { + sum(1, 2) + sum(3, 4) + globalNum; + }; + + outer() + globalNum; + `, + expected: 50, + }, + } + + runVmTests(t, tests) +} + +func TestCallingFunctionsWithWrongArguments(t *testing.T) { + tests := []vmTestCase{ + { + input: `fn() { 1; }(1);`, + expected: `wrong number of arguments: want=0, got=1`, + }, + { + input: `fn(a) { a; }();`, + expected: `wrong number of arguments: want=1, got=0`, + }, + { + input: `fn(a, b) { a + b; }(1);`, + expected: `wrong number of arguments: want=2, got=1`, + }, + } + + for _, tt := range tests { + program := parse(tt.input) + + comp := compiler.New() + err := comp.Compile(program) + if err != nil { + t.Fatalf("compiler error: %s", err) + } + + vm := New(comp.Bytecode()) + err = vm.Run() + if err == nil { + t.Fatalf("expected VM error but resulted in none.") + } + + if err.Error() != tt.expected { + t.Fatalf("wrong VM error: want=%q, got=%q", tt.expected, err) + } + } +} + +func TestBuiltinFunctions(t *testing.T) { + tests := []vmTestCase{ + {`len("")`, 0}, + {`len("four")`, 4}, + {`len("hello world")`, 11}, + { + `len(1)`, + &object.Error{ + Message: "argument to `len` not supported, got INTEGER", + }, + }, + {`len("one", "two")`, + &object.Error{ + Message: "wrong number of arguments. got=2, want=1", + }, + }, + {`len([1, 2, 3])`, 3}, + {`len([])`, 0}, + {`puts("hello", "world!")`, Null}, + {`first([1, 2, 3])`, 1}, + {`first([])`, Null}, + {`first(1)`, + &object.Error{ + Message: "argument to `first` must be ARRAY, got INTEGER", + }, + }, + {`last([1, 2, 3])`, 3}, + {`last([])`, Null}, + {`last(1)`, + &object.Error{ + Message: "argument to `last` must be ARRAY, got INTEGER", + }, + }, + {`rest([1, 2, 3])`, []int{2, 3}}, + {`rest([])`, Null}, + {`push([], 1)`, []int{1}}, + {`push(1, 1)`, + &object.Error{ + Message: "argument to `push` must be ARRAY, got INTEGER", + }, + }, + } + + runVmTests(t, tests) +} + +type vmTestCase struct { + input string + expected interface{} +} + +func runVmTests(t *testing.T, tests []vmTestCase) { + t.Helper() + + for _, tt := range tests { + program := parse(tt.input) + + comp := compiler.New() + err := comp.Compile(program) + if err != nil { + t.Fatalf("compiler error: %s", err) + } + + vm := New(comp.Bytecode()) + err = vm.Run() + if err != nil { + t.Fatalf("vm error: %s", err) + } + + stackElem := vm.LastPoppedStackElem() + + testExpectedObject(t, tt.expected, stackElem) + } +} + +func parse(input string) *ast.Program { + l := lexer.New(input) + p := parser.New(l) + return p.ParseProgram() +} + +func testExpectedObject( + t *testing.T, + expected interface{}, + actual object.Object, +) { + t.Helper() + + switch expected := expected.(type) { + case int: + err := testIntegerObject(int64(expected), actual) + if err != nil { + t.Errorf("testIntegerObject failed: %s", err) + } + + case bool: + err := testBooleanObject(bool(expected), actual) + if err != nil { + t.Errorf("testBooleanObject failed: %s", err) + } + + case *object.Null: + if actual != Null { + t.Errorf("object is not Null: %T (%+v)", actual, actual) + } + + case string: + err := testStringObject(expected, actual) + if err != nil { + t.Errorf("testStringObject failed: %s", err) + } + + case []int: + array, ok := actual.(*object.Array) + if !ok { + t.Errorf("object not Array: %T (%+v)", actual, actual) + return + } + + if len(array.Elements) != len(expected) { + t.Errorf("wrong num of elements. want=%d, got=%d", + len(expected), len(array.Elements)) + return + } + + for i, expectedElem := range expected { + err := testIntegerObject(int64(expectedElem), array.Elements[i]) + if err != nil { + t.Errorf("testIntegerObject failed: %s", err) + } + } + + case map[object.HashKey]int64: + hash, ok := actual.(*object.Hash) + if !ok { + t.Errorf("object is not Hash. got=%T (%+v)", actual, actual) + return + } + + if len(hash.Pairs) != len(expected) { + t.Errorf("hash has wrong number of Pairs. want=%d, got=%d", + len(expected), len(hash.Pairs)) + return + } + + for expectedKey, expectedValue := range expected { + pair, ok := hash.Pairs[expectedKey] + if !ok { + t.Errorf("no pair for given key in Pairs") + } + + err := testIntegerObject(expectedValue, pair.Value) + if err != nil { + t.Errorf("testIntegerObject failed: %s", err) + } + } + + case *object.Error: + errObj, ok := actual.(*object.Error) + if !ok { + t.Errorf("object is not Error: %T (%+v)", actual, actual) + return + } + if errObj.Message != expected.Message { + t.Errorf("wrong error message. expected=%q, got=%q", + expected.Message, errObj.Message) + } + } +} + +func testIntegerObject(expected int64, actual object.Object) error { + result, ok := actual.(*object.Integer) + if !ok { + return fmt.Errorf("object is not Integer. got=%T (%+v)", + actual, actual) + } + + if result.Value != expected { + return fmt.Errorf("object has wrong value. got=%d, want=%d", + result.Value, expected) + } + + return nil +} + +func testBooleanObject(expected bool, actual object.Object) error { + result, ok := actual.(*object.Boolean) + if !ok { + return fmt.Errorf("object is not Boolean. got=%T (%+v)", + actual, actual) + } + + if result.Value != expected { + return fmt.Errorf("object has wrong value. got=%t, want=%t", + result.Value, expected) + } + + return nil +} + +func testStringObject(expected string, actual object.Object) error { + result, ok := actual.(*object.String) + if !ok { + return fmt.Errorf("object is not String. got=%T (%+v)", + actual, actual) + } + + if result.Value != expected { + return fmt.Errorf("object has wrong value. got=%q, want=%q", + result.Value, expected) + } + + return nil +} diff --git a/wcig_code_1_2/09/.envrc b/wcig_code_1_2/09/.envrc new file mode 100644 index 0000000..fb65ef4 --- /dev/null +++ b/wcig_code_1_2/09/.envrc @@ -0,0 +1 @@ +export GOPATH=$(pwd) diff --git a/wcig_code_1_2/09/src/monkey/ast/ast.go b/wcig_code_1_2/09/src/monkey/ast/ast.go new file mode 100644 index 0000000..fb9d497 --- /dev/null +++ b/wcig_code_1_2/09/src/monkey/ast/ast.go @@ -0,0 +1,344 @@ +package ast + +import ( + "bytes" + "fmt" + "monkey/token" + "strings" +) + +// The base Node interface +type Node interface { + TokenLiteral() string + String() string +} + +// All statement nodes implement this +type Statement interface { + Node + statementNode() +} + +// All expression nodes implement this +type Expression interface { + Node + expressionNode() +} + +type Program struct { + Statements []Statement +} + +func (p *Program) TokenLiteral() string { + if len(p.Statements) > 0 { + return p.Statements[0].TokenLiteral() + } else { + return "" + } +} + +func (p *Program) String() string { + var out bytes.Buffer + + for _, s := range p.Statements { + out.WriteString(s.String()) + } + + return out.String() +} + +// Statements +type LetStatement struct { + Token token.Token // the token.LET token + Name *Identifier + Value Expression +} + +func (ls *LetStatement) statementNode() {} +func (ls *LetStatement) TokenLiteral() string { return ls.Token.Literal } +func (ls *LetStatement) String() string { + var out bytes.Buffer + + out.WriteString(ls.TokenLiteral() + " ") + out.WriteString(ls.Name.String()) + out.WriteString(" = ") + + if ls.Value != nil { + out.WriteString(ls.Value.String()) + } + + out.WriteString(";") + + return out.String() +} + +type ReturnStatement struct { + Token token.Token // the 'return' token + ReturnValue Expression +} + +func (rs *ReturnStatement) statementNode() {} +func (rs *ReturnStatement) TokenLiteral() string { return rs.Token.Literal } +func (rs *ReturnStatement) String() string { + var out bytes.Buffer + + out.WriteString(rs.TokenLiteral() + " ") + + if rs.ReturnValue != nil { + out.WriteString(rs.ReturnValue.String()) + } + + out.WriteString(";") + + return out.String() +} + +type ExpressionStatement struct { + Token token.Token // the first token of the expression + Expression Expression +} + +func (es *ExpressionStatement) statementNode() {} +func (es *ExpressionStatement) TokenLiteral() string { return es.Token.Literal } +func (es *ExpressionStatement) String() string { + if es.Expression != nil { + return es.Expression.String() + } + return "" +} + +type BlockStatement struct { + Token token.Token // the { token + Statements []Statement +} + +func (bs *BlockStatement) statementNode() {} +func (bs *BlockStatement) TokenLiteral() string { return bs.Token.Literal } +func (bs *BlockStatement) String() string { + var out bytes.Buffer + + for _, s := range bs.Statements { + out.WriteString(s.String()) + } + + return out.String() +} + +// Expressions +type Identifier struct { + Token token.Token // the token.IDENT token + Value string +} + +func (i *Identifier) expressionNode() {} +func (i *Identifier) TokenLiteral() string { return i.Token.Literal } +func (i *Identifier) String() string { return i.Value } + +type Boolean struct { + Token token.Token + Value bool +} + +func (b *Boolean) expressionNode() {} +func (b *Boolean) TokenLiteral() string { return b.Token.Literal } +func (b *Boolean) String() string { return b.Token.Literal } + +type IntegerLiteral struct { + Token token.Token + Value int64 +} + +func (il *IntegerLiteral) expressionNode() {} +func (il *IntegerLiteral) TokenLiteral() string { return il.Token.Literal } +func (il *IntegerLiteral) String() string { return il.Token.Literal } + +type PrefixExpression struct { + Token token.Token // The prefix token, e.g. ! + Operator string + Right Expression +} + +func (pe *PrefixExpression) expressionNode() {} +func (pe *PrefixExpression) TokenLiteral() string { return pe.Token.Literal } +func (pe *PrefixExpression) String() string { + var out bytes.Buffer + + out.WriteString("(") + out.WriteString(pe.Operator) + out.WriteString(pe.Right.String()) + out.WriteString(")") + + return out.String() +} + +type InfixExpression struct { + Token token.Token // The operator token, e.g. + + Left Expression + Operator string + Right Expression +} + +func (oe *InfixExpression) expressionNode() {} +func (oe *InfixExpression) TokenLiteral() string { return oe.Token.Literal } +func (oe *InfixExpression) String() string { + var out bytes.Buffer + + out.WriteString("(") + out.WriteString(oe.Left.String()) + out.WriteString(" " + oe.Operator + " ") + out.WriteString(oe.Right.String()) + out.WriteString(")") + + return out.String() +} + +type IfExpression struct { + Token token.Token // The 'if' token + Condition Expression + Consequence *BlockStatement + Alternative *BlockStatement +} + +func (ie *IfExpression) expressionNode() {} +func (ie *IfExpression) TokenLiteral() string { return ie.Token.Literal } +func (ie *IfExpression) String() string { + var out bytes.Buffer + + out.WriteString("if") + out.WriteString(ie.Condition.String()) + out.WriteString(" ") + out.WriteString(ie.Consequence.String()) + + if ie.Alternative != nil { + out.WriteString("else ") + out.WriteString(ie.Alternative.String()) + } + + return out.String() +} + +type FunctionLiteral struct { + Token token.Token // The 'fn' token + Parameters []*Identifier + Body *BlockStatement + Name string +} + +func (fl *FunctionLiteral) expressionNode() {} +func (fl *FunctionLiteral) TokenLiteral() string { return fl.Token.Literal } +func (fl *FunctionLiteral) String() string { + var out bytes.Buffer + + params := []string{} + for _, p := range fl.Parameters { + params = append(params, p.String()) + } + + out.WriteString(fl.TokenLiteral()) + if fl.Name != "" { + out.WriteString(fmt.Sprintf("<%s>", fl.Name)) + } + out.WriteString("(") + out.WriteString(strings.Join(params, ", ")) + out.WriteString(") ") + out.WriteString(fl.Body.String()) + + return out.String() +} + +type CallExpression struct { + Token token.Token // The '(' token + Function Expression // Identifier or FunctionLiteral + Arguments []Expression +} + +func (ce *CallExpression) expressionNode() {} +func (ce *CallExpression) TokenLiteral() string { return ce.Token.Literal } +func (ce *CallExpression) String() string { + var out bytes.Buffer + + args := []string{} + for _, a := range ce.Arguments { + args = append(args, a.String()) + } + + out.WriteString(ce.Function.String()) + out.WriteString("(") + out.WriteString(strings.Join(args, ", ")) + out.WriteString(")") + + return out.String() +} + +type StringLiteral struct { + Token token.Token + Value string +} + +func (sl *StringLiteral) expressionNode() {} +func (sl *StringLiteral) TokenLiteral() string { return sl.Token.Literal } +func (sl *StringLiteral) String() string { return sl.Token.Literal } + +type ArrayLiteral struct { + Token token.Token // the '[' token + Elements []Expression +} + +func (al *ArrayLiteral) expressionNode() {} +func (al *ArrayLiteral) TokenLiteral() string { return al.Token.Literal } +func (al *ArrayLiteral) String() string { + var out bytes.Buffer + + elements := []string{} + for _, el := range al.Elements { + elements = append(elements, el.String()) + } + + out.WriteString("[") + out.WriteString(strings.Join(elements, ", ")) + out.WriteString("]") + + return out.String() +} + +type IndexExpression struct { + Token token.Token // The [ token + Left Expression + Index Expression +} + +func (ie *IndexExpression) expressionNode() {} +func (ie *IndexExpression) TokenLiteral() string { return ie.Token.Literal } +func (ie *IndexExpression) String() string { + var out bytes.Buffer + + out.WriteString("(") + out.WriteString(ie.Left.String()) + out.WriteString("[") + out.WriteString(ie.Index.String()) + out.WriteString("])") + + return out.String() +} + +type HashLiteral struct { + Token token.Token // the '{' token + Pairs map[Expression]Expression +} + +func (hl *HashLiteral) expressionNode() {} +func (hl *HashLiteral) TokenLiteral() string { return hl.Token.Literal } +func (hl *HashLiteral) String() string { + var out bytes.Buffer + + pairs := []string{} + for key, value := range hl.Pairs { + pairs = append(pairs, key.String()+":"+value.String()) + } + + out.WriteString("{") + out.WriteString(strings.Join(pairs, ", ")) + out.WriteString("}") + + return out.String() +} diff --git a/wcig_code_1_2/09/src/monkey/ast/ast_test.go b/wcig_code_1_2/09/src/monkey/ast/ast_test.go new file mode 100644 index 0000000..14a49dc --- /dev/null +++ b/wcig_code_1_2/09/src/monkey/ast/ast_test.go @@ -0,0 +1,28 @@ +package ast + +import ( + "monkey/token" + "testing" +) + +func TestString(t *testing.T) { + program := &Program{ + Statements: []Statement{ + &LetStatement{ + Token: token.Token{Type: token.LET, Literal: "let"}, + Name: &Identifier{ + Token: token.Token{Type: token.IDENT, Literal: "myVar"}, + Value: "myVar", + }, + Value: &Identifier{ + Token: token.Token{Type: token.IDENT, Literal: "anotherVar"}, + Value: "anotherVar", + }, + }, + }, + } + + if program.String() != "let myVar = anotherVar;" { + t.Errorf("program.String() wrong. got=%q", program.String()) + } +} diff --git a/wcig_code_1_2/09/src/monkey/code/code.go b/wcig_code_1_2/09/src/monkey/code/code.go new file mode 100644 index 0000000..24d81b8 --- /dev/null +++ b/wcig_code_1_2/09/src/monkey/code/code.go @@ -0,0 +1,219 @@ +package code + +import ( + "bytes" + "encoding/binary" + "fmt" +) + +type Instructions []byte + +func (ins Instructions) String() string { + var out bytes.Buffer + + i := 0 + for i < len(ins) { + def, err := Lookup(ins[i]) + if err != nil { + fmt.Fprintf(&out, "ERROR: %s\n", err) + continue + } + + operands, read := ReadOperands(def, ins[i+1:]) + + fmt.Fprintf(&out, "%04d %s\n", i, ins.fmtInstruction(def, operands)) + + i += 1 + read + } + + return out.String() +} + +func (ins Instructions) fmtInstruction(def *Definition, operands []int) string { + operandCount := len(def.OperandWidths) + + if len(operands) != operandCount { + return fmt.Sprintf("ERROR: operand len %d does not match defined %d\n", + len(operands), operandCount) + } + + switch operandCount { + case 0: + return def.Name + case 1: + return fmt.Sprintf("%s %d", def.Name, operands[0]) + case 2: + return fmt.Sprintf("%s %d %d", def.Name, operands[0], operands[1]) + } + + return fmt.Sprintf("ERROR: unhandled operandCount for %s\n", def.Name) +} + +type Opcode byte + +const ( + OpConstant Opcode = iota + + OpAdd + + OpPop + + OpSub + OpMul + OpDiv + + OpTrue + OpFalse + + OpEqual + OpNotEqual + OpGreaterThan + + OpMinus + OpBang + + OpJumpNotTruthy + OpJump + + OpNull + + OpGetGlobal + OpSetGlobal + + OpArray + OpHash + OpIndex + + OpCall + + OpReturnValue + OpReturn + + OpGetLocal + OpSetLocal + + OpGetBuiltin + + OpClosure + + OpGetFree + + OpCurrentClosure +) + +type Definition struct { + Name string + OperandWidths []int +} + +var definitions = map[Opcode]*Definition{ + OpConstant: {"OpConstant", []int{2}}, + + OpAdd: {"OpAdd", []int{}}, + + OpPop: {"OpPop", []int{}}, + + OpSub: {"OpSub", []int{}}, + OpMul: {"OpMul", []int{}}, + OpDiv: {"OpDiv", []int{}}, + + OpTrue: {"OpTrue", []int{}}, + OpFalse: {"OpFalse", []int{}}, + + OpEqual: {"OpEqual", []int{}}, + OpNotEqual: {"OpNotEqual", []int{}}, + OpGreaterThan: {"OpGreaterThan", []int{}}, + + OpMinus: {"OpMinus", []int{}}, + OpBang: {"OpBang", []int{}}, + + OpJumpNotTruthy: {"OpJumpNotTruthy", []int{2}}, + OpJump: {"OpJump", []int{2}}, + + OpNull: {"OpNull", []int{}}, + + OpGetGlobal: {"OpGetGlobal", []int{2}}, + OpSetGlobal: {"OpSetGlobal", []int{2}}, + + OpArray: {"OpArray", []int{2}}, + OpHash: {"OpHash", []int{2}}, + OpIndex: {"OpIndex", []int{}}, + + OpCall: {"OpCall", []int{1}}, + + OpReturnValue: {"OpReturnValue", []int{}}, + OpReturn: {"OpReturn", []int{}}, + + OpGetLocal: {"OpGetLocal", []int{1}}, + OpSetLocal: {"OpSetLocal", []int{1}}, + + OpGetBuiltin: {"OpGetBuiltin", []int{1}}, + + OpClosure: {"OpClosure", []int{2, 1}}, + + OpGetFree: {"OpGetFree", []int{1}}, + + OpCurrentClosure: {"OpCurrentClosure", []int{}}, +} + +func Lookup(op byte) (*Definition, error) { + def, ok := definitions[Opcode(op)] + if !ok { + return nil, fmt.Errorf("opcode %d undefined", op) + } + + return def, nil +} + +func Make(op Opcode, operands ...int) []byte { + def, ok := definitions[op] + if !ok { + return []byte{} + } + + instructionLen := 1 + for _, w := range def.OperandWidths { + instructionLen += w + } + + instruction := make([]byte, instructionLen) + instruction[0] = byte(op) + + offset := 1 + for i, o := range operands { + width := def.OperandWidths[i] + switch width { + case 2: + binary.BigEndian.PutUint16(instruction[offset:], uint16(o)) + case 1: + instruction[offset] = byte(o) + } + offset += width + } + + return instruction +} + +func ReadOperands(def *Definition, ins Instructions) ([]int, int) { + operands := make([]int, len(def.OperandWidths)) + offset := 0 + + for i, width := range def.OperandWidths { + switch width { + case 2: + operands[i] = int(ReadUint16(ins[offset:])) + case 1: + operands[i] = int(ReadUint8(ins[offset:])) + } + + offset += width + } + + return operands, offset +} + +func ReadUint8(ins Instructions) uint8 { return uint8(ins[0]) } + +func ReadUint16(ins Instructions) uint16 { + return binary.BigEndian.Uint16(ins) +} diff --git a/wcig_code_1_2/09/src/monkey/code/code_test.go b/wcig_code_1_2/09/src/monkey/code/code_test.go new file mode 100644 index 0000000..060500c --- /dev/null +++ b/wcig_code_1_2/09/src/monkey/code/code_test.go @@ -0,0 +1,91 @@ +package code + +import "testing" + +func TestMake(t *testing.T) { + tests := []struct { + op Opcode + operands []int + expected []byte + }{ + {OpConstant, []int{65534}, []byte{byte(OpConstant), 255, 254}}, + {OpAdd, []int{}, []byte{byte(OpAdd)}}, + {OpGetLocal, []int{255}, []byte{byte(OpGetLocal), 255}}, + {OpClosure, []int{65534, 255}, []byte{byte(OpClosure), 255, 254, 255}}, + } + + for _, tt := range tests { + instruction := Make(tt.op, tt.operands...) + + if len(instruction) != len(tt.expected) { + t.Errorf("instruction has wrong length. want=%d, got=%d", + len(tt.expected), len(instruction)) + } + + for i, b := range tt.expected { + if instruction[i] != tt.expected[i] { + t.Errorf("wrong byte at pos %d. want=%d, got=%d", + i, b, instruction[i]) + } + } + } +} + +func TestInstructionsString(t *testing.T) { + instructions := []Instructions{ + Make(OpAdd), + Make(OpGetLocal, 1), + Make(OpConstant, 2), + Make(OpConstant, 65535), + Make(OpClosure, 65535, 255), + } + + expected := `0000 OpAdd +0001 OpGetLocal 1 +0003 OpConstant 2 +0006 OpConstant 65535 +0009 OpClosure 65535 255 +` + + concatted := Instructions{} + for _, ins := range instructions { + concatted = append(concatted, ins...) + } + + if concatted.String() != expected { + t.Errorf("instructions wrongly formatted.\nwant=%q\ngot=%q", + expected, concatted.String()) + } +} + +func TestReadOperands(t *testing.T) { + tests := []struct { + op Opcode + operands []int + bytesRead int + }{ + {OpConstant, []int{65535}, 2}, + {OpGetLocal, []int{255}, 1}, + {OpClosure, []int{65535, 255}, 3}, + } + + for _, tt := range tests { + instruction := Make(tt.op, tt.operands...) + + def, err := Lookup(byte(tt.op)) + if err != nil { + t.Fatalf("definition not found: %q\n", err) + } + + operandsRead, n := ReadOperands(def, instruction[1:]) + if n != tt.bytesRead { + t.Fatalf("n wrong. want=%d, got=%d", tt.bytesRead, n) + } + + for i, want := range tt.operands { + if operandsRead[i] != want { + t.Errorf("operand wrong. want=%d, got=%d", want, operandsRead[i]) + } + } + } +} diff --git a/wcig_code_1_2/09/src/monkey/compiler/compiler.go b/wcig_code_1_2/09/src/monkey/compiler/compiler.go new file mode 100644 index 0000000..ff9a840 --- /dev/null +++ b/wcig_code_1_2/09/src/monkey/compiler/compiler.go @@ -0,0 +1,456 @@ +package compiler + +import ( + "fmt" + "monkey/ast" + "monkey/code" + "monkey/object" + "sort" +) + +type Compiler struct { + constants []object.Object + + symbolTable *SymbolTable + + scopes []CompilationScope + scopeIndex int +} + +func New() *Compiler { + mainScope := CompilationScope{ + instructions: code.Instructions{}, + lastInstruction: EmittedInstruction{}, + previousInstruction: EmittedInstruction{}, + } + + symbolTable := NewSymbolTable() + + for i, v := range object.Builtins { + symbolTable.DefineBuiltin(i, v.Name) + } + + return &Compiler{ + constants: []object.Object{}, + symbolTable: symbolTable, + scopes: []CompilationScope{mainScope}, + scopeIndex: 0, + } +} + +func NewWithState(s *SymbolTable, constants []object.Object) *Compiler { + compiler := New() + compiler.symbolTable = s + compiler.constants = constants + return compiler +} + +func (c *Compiler) Compile(node ast.Node) error { + switch node := node.(type) { + case *ast.Program: + for _, s := range node.Statements { + err := c.Compile(s) + if err != nil { + return err + } + } + + case *ast.ExpressionStatement: + err := c.Compile(node.Expression) + if err != nil { + return err + } + c.emit(code.OpPop) + + case *ast.InfixExpression: + if node.Operator == "<" { + err := c.Compile(node.Right) + if err != nil { + return err + } + + err = c.Compile(node.Left) + if err != nil { + return err + } + c.emit(code.OpGreaterThan) + return nil + } + + err := c.Compile(node.Left) + if err != nil { + return err + } + + err = c.Compile(node.Right) + if err != nil { + return err + } + + switch node.Operator { + case "+": + c.emit(code.OpAdd) + case "-": + c.emit(code.OpSub) + case "*": + c.emit(code.OpMul) + case "/": + c.emit(code.OpDiv) + case ">": + c.emit(code.OpGreaterThan) + case "==": + c.emit(code.OpEqual) + case "!=": + c.emit(code.OpNotEqual) + default: + return fmt.Errorf("unknown operator %s", node.Operator) + } + + case *ast.IntegerLiteral: + integer := &object.Integer{Value: node.Value} + c.emit(code.OpConstant, c.addConstant(integer)) + + case *ast.Boolean: + if node.Value { + c.emit(code.OpTrue) + } else { + c.emit(code.OpFalse) + } + + case *ast.PrefixExpression: + err := c.Compile(node.Right) + if err != nil { + return err + } + + switch node.Operator { + case "!": + c.emit(code.OpBang) + case "-": + c.emit(code.OpMinus) + default: + return fmt.Errorf("unknown operator %s", node.Operator) + } + + case *ast.IfExpression: + err := c.Compile(node.Condition) + if err != nil { + return err + } + + // Emit an `OpJumpNotTruthy` with a bogus value + jumpNotTruthyPos := c.emit(code.OpJumpNotTruthy, 9999) + + err = c.Compile(node.Consequence) + if err != nil { + return err + } + + if c.lastInstructionIs(code.OpPop) { + c.removeLastPop() + } + + // Emit an `OpJump` with a bogus value + jumpPos := c.emit(code.OpJump, 9999) + + afterConsequencePos := len(c.currentInstructions()) + c.changeOperand(jumpNotTruthyPos, afterConsequencePos) + + if node.Alternative == nil { + c.emit(code.OpNull) + } else { + err := c.Compile(node.Alternative) + if err != nil { + return err + } + + if c.lastInstructionIs(code.OpPop) { + c.removeLastPop() + } + } + + afterAlternativePos := len(c.currentInstructions()) + c.changeOperand(jumpPos, afterAlternativePos) + + case *ast.BlockStatement: + for _, s := range node.Statements { + err := c.Compile(s) + if err != nil { + return err + } + } + + case *ast.LetStatement: + symbol := c.symbolTable.Define(node.Name.Value) + err := c.Compile(node.Value) + if err != nil { + return err + } + + if symbol.Scope == GlobalScope { + c.emit(code.OpSetGlobal, symbol.Index) + } else { + c.emit(code.OpSetLocal, symbol.Index) + } + + case *ast.Identifier: + symbol, ok := c.symbolTable.Resolve(node.Value) + if !ok { + return fmt.Errorf("undefined variable %s", node.Value) + } + + c.loadSymbol(symbol) + + case *ast.StringLiteral: + str := &object.String{Value: node.Value} + c.emit(code.OpConstant, c.addConstant(str)) + + case *ast.ArrayLiteral: + for _, el := range node.Elements { + err := c.Compile(el) + if err != nil { + return err + } + } + + c.emit(code.OpArray, len(node.Elements)) + + case *ast.HashLiteral: + keys := []ast.Expression{} + for k := range node.Pairs { + keys = append(keys, k) + } + sort.Slice(keys, func(i, j int) bool { + return keys[i].String() < keys[j].String() + }) + + for _, k := range keys { + err := c.Compile(k) + if err != nil { + return err + } + err = c.Compile(node.Pairs[k]) + if err != nil { + return err + } + } + + c.emit(code.OpHash, len(node.Pairs)*2) + + case *ast.IndexExpression: + err := c.Compile(node.Left) + if err != nil { + return err + } + + err = c.Compile(node.Index) + if err != nil { + return err + } + + c.emit(code.OpIndex) + + case *ast.FunctionLiteral: + c.enterScope() + + if node.Name != "" { + c.symbolTable.DefineFunctionName(node.Name) + } + + for _, p := range node.Parameters { + c.symbolTable.Define(p.Value) + } + + err := c.Compile(node.Body) + if err != nil { + return err + } + + if c.lastInstructionIs(code.OpPop) { + c.replaceLastPopWithReturn() + } + if !c.lastInstructionIs(code.OpReturnValue) { + c.emit(code.OpReturn) + } + + freeSymbols := c.symbolTable.FreeSymbols + numLocals := c.symbolTable.numDefinitions + instructions := c.leaveScope() + + for _, s := range freeSymbols { + c.loadSymbol(s) + } + + compiledFn := &object.CompiledFunction{ + Instructions: instructions, + NumLocals: numLocals, + NumParameters: len(node.Parameters), + } + + fnIndex := c.addConstant(compiledFn) + c.emit(code.OpClosure, fnIndex, len(freeSymbols)) + + case *ast.ReturnStatement: + err := c.Compile(node.ReturnValue) + if err != nil { + return err + } + + c.emit(code.OpReturnValue) + + case *ast.CallExpression: + err := c.Compile(node.Function) + if err != nil { + return err + } + + for _, a := range node.Arguments { + err := c.Compile(a) + if err != nil { + return err + } + } + + c.emit(code.OpCall, len(node.Arguments)) + + } + + return nil +} + +func (c *Compiler) Bytecode() *Bytecode { + return &Bytecode{ + Instructions: c.currentInstructions(), + Constants: c.constants, + } +} + +func (c *Compiler) addConstant(obj object.Object) int { + c.constants = append(c.constants, obj) + return len(c.constants) - 1 +} + +func (c *Compiler) emit(op code.Opcode, operands ...int) int { + ins := code.Make(op, operands...) + pos := c.addInstruction(ins) + + c.setLastInstruction(op, pos) + + return pos +} + +func (c *Compiler) addInstruction(ins []byte) int { + posNewInstruction := len(c.currentInstructions()) + updatedInstructions := append(c.currentInstructions(), ins...) + + c.scopes[c.scopeIndex].instructions = updatedInstructions + + return posNewInstruction +} + +func (c *Compiler) setLastInstruction(op code.Opcode, pos int) { + previous := c.scopes[c.scopeIndex].lastInstruction + last := EmittedInstruction{Opcode: op, Position: pos} + + c.scopes[c.scopeIndex].previousInstruction = previous + c.scopes[c.scopeIndex].lastInstruction = last +} + +func (c *Compiler) lastInstructionIs(op code.Opcode) bool { + if len(c.currentInstructions()) == 0 { + return false + } + + return c.scopes[c.scopeIndex].lastInstruction.Opcode == op +} + +func (c *Compiler) removeLastPop() { + last := c.scopes[c.scopeIndex].lastInstruction + previous := c.scopes[c.scopeIndex].previousInstruction + + old := c.currentInstructions() + new := old[:last.Position] + + c.scopes[c.scopeIndex].instructions = new + c.scopes[c.scopeIndex].lastInstruction = previous +} + +func (c *Compiler) replaceInstruction(pos int, newInstruction []byte) { + ins := c.currentInstructions() + + for i := 0; i < len(newInstruction); i++ { + ins[pos+i] = newInstruction[i] + } +} + +func (c *Compiler) changeOperand(opPos int, operand int) { + op := code.Opcode(c.currentInstructions()[opPos]) + newInstruction := code.Make(op, operand) + + c.replaceInstruction(opPos, newInstruction) +} + +func (c *Compiler) currentInstructions() code.Instructions { + return c.scopes[c.scopeIndex].instructions +} + +func (c *Compiler) enterScope() { + scope := CompilationScope{ + instructions: code.Instructions{}, + lastInstruction: EmittedInstruction{}, + previousInstruction: EmittedInstruction{}, + } + c.scopes = append(c.scopes, scope) + c.scopeIndex++ + + c.symbolTable = NewEnclosedSymbolTable(c.symbolTable) +} + +func (c *Compiler) leaveScope() code.Instructions { + instructions := c.currentInstructions() + + c.scopes = c.scopes[:len(c.scopes)-1] + c.scopeIndex-- + + c.symbolTable = c.symbolTable.Outer + + return instructions +} + +func (c *Compiler) replaceLastPopWithReturn() { + lastPos := c.scopes[c.scopeIndex].lastInstruction.Position + c.replaceInstruction(lastPos, code.Make(code.OpReturnValue)) + + c.scopes[c.scopeIndex].lastInstruction.Opcode = code.OpReturnValue +} + +func (c *Compiler) loadSymbol(s Symbol) { + switch s.Scope { + case GlobalScope: + c.emit(code.OpGetGlobal, s.Index) + case LocalScope: + c.emit(code.OpGetLocal, s.Index) + case BuiltinScope: + c.emit(code.OpGetBuiltin, s.Index) + case FreeScope: + c.emit(code.OpGetFree, s.Index) + case FunctionScope: + c.emit(code.OpCurrentClosure) + } +} + +type Bytecode struct { + Instructions code.Instructions + Constants []object.Object +} + +type EmittedInstruction struct { + Opcode code.Opcode + Position int +} + +type CompilationScope struct { + instructions code.Instructions + lastInstruction EmittedInstruction + previousInstruction EmittedInstruction +} diff --git a/wcig_code_1_2/09/src/monkey/compiler/compiler_test.go b/wcig_code_1_2/09/src/monkey/compiler/compiler_test.go new file mode 100644 index 0000000..7f3e248 --- /dev/null +++ b/wcig_code_1_2/09/src/monkey/compiler/compiler_test.go @@ -0,0 +1,1106 @@ +package compiler + +import ( + "fmt" + "monkey/ast" + "monkey/code" + "monkey/lexer" + "monkey/object" + "monkey/parser" + "testing" +) + +func TestCompilerScopes(t *testing.T) { + compiler := New() + if compiler.scopeIndex != 0 { + t.Errorf("scopeIndex wrong. got=%d, want=%d", compiler.scopeIndex, 0) + } + globalSymbolTable := compiler.symbolTable + + compiler.emit(code.OpMul) + + compiler.enterScope() + if compiler.scopeIndex != 1 { + t.Errorf("scopeIndex wrong. got=%d, want=%d", compiler.scopeIndex, 1) + } + + compiler.emit(code.OpSub) + + if len(compiler.scopes[compiler.scopeIndex].instructions) != 1 { + t.Errorf("instructions length wrong. got=%d", + len(compiler.scopes[compiler.scopeIndex].instructions)) + } + + last := compiler.scopes[compiler.scopeIndex].lastInstruction + if last.Opcode != code.OpSub { + t.Errorf("lastInstruction.Opcode wrong. got=%d, want=%d", + last.Opcode, code.OpSub) + } + + if compiler.symbolTable.Outer != globalSymbolTable { + t.Errorf("compiler did not enclose symbolTable") + } + + compiler.leaveScope() + if compiler.scopeIndex != 0 { + t.Errorf("scopeIndex wrong. got=%d, want=%d", + compiler.scopeIndex, 0) + } + + if compiler.symbolTable != globalSymbolTable { + t.Errorf("compiler did not restore global symbol table") + } + if compiler.symbolTable.Outer != nil { + t.Errorf("compiler modified global symbol table incorrectly") + } + + compiler.emit(code.OpAdd) + + if len(compiler.scopes[compiler.scopeIndex].instructions) != 2 { + t.Errorf("instructions length wrong. got=%d", + len(compiler.scopes[compiler.scopeIndex].instructions)) + } + + last = compiler.scopes[compiler.scopeIndex].lastInstruction + if last.Opcode != code.OpAdd { + t.Errorf("lastInstruction.Opcode wrong. got=%d, want=%d", + last.Opcode, code.OpAdd) + } + + previous := compiler.scopes[compiler.scopeIndex].previousInstruction + if previous.Opcode != code.OpMul { + t.Errorf("previousInstruction.Opcode wrong. got=%d, want=%d", + previous.Opcode, code.OpMul) + } +} + +func TestIntegerArithmetic(t *testing.T) { + tests := []compilerTestCase{ + { + input: "1 + 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpAdd), + code.Make(code.OpPop), + }, + }, + { + input: "1; 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpPop), + code.Make(code.OpConstant, 1), + code.Make(code.OpPop), + }, + }, + { + input: "1 - 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpSub), + code.Make(code.OpPop), + }, + }, + { + input: "1 * 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpMul), + code.Make(code.OpPop), + }, + }, + { + input: "2 / 1", + expectedConstants: []interface{}{2, 1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpDiv), + code.Make(code.OpPop), + }, + }, + { + input: "-1", + expectedConstants: []interface{}{1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpMinus), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestBooleanExpressions(t *testing.T) { + tests := []compilerTestCase{ + { + input: "true", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpTrue), + code.Make(code.OpPop), + }, + }, + { + input: "false", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpFalse), + code.Make(code.OpPop), + }, + }, + { + input: "1 > 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpGreaterThan), + code.Make(code.OpPop), + }, + }, + { + input: "1 < 2", + expectedConstants: []interface{}{2, 1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpGreaterThan), + code.Make(code.OpPop), + }, + }, + { + input: "1 == 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpEqual), + code.Make(code.OpPop), + }, + }, + { + input: "1 != 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpNotEqual), + code.Make(code.OpPop), + }, + }, + { + input: "true == false", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpTrue), + code.Make(code.OpFalse), + code.Make(code.OpEqual), + code.Make(code.OpPop), + }, + }, + { + input: "true != false", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpTrue), + code.Make(code.OpFalse), + code.Make(code.OpNotEqual), + code.Make(code.OpPop), + }, + }, + { + input: "!true", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpTrue), + code.Make(code.OpBang), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestConditionals(t *testing.T) { + tests := []compilerTestCase{ + { + input: ` + if (true) { 10 }; 3333; + `, + expectedConstants: []interface{}{10, 3333}, + expectedInstructions: []code.Instructions{ + // 0000 + code.Make(code.OpTrue), + // 0001 + code.Make(code.OpJumpNotTruthy, 10), + // 0004 + code.Make(code.OpConstant, 0), + // 0007 + code.Make(code.OpJump, 11), + // 0010 + code.Make(code.OpNull), + // 0011 + code.Make(code.OpPop), + // 0012 + code.Make(code.OpConstant, 1), + // 0015 + code.Make(code.OpPop), + }, + }, + { + input: ` + if (true) { 10 } else { 20 }; 3333; + `, + expectedConstants: []interface{}{10, 20, 3333}, + expectedInstructions: []code.Instructions{ + // 0000 + code.Make(code.OpTrue), + // 0001 + code.Make(code.OpJumpNotTruthy, 10), + // 0004 + code.Make(code.OpConstant, 0), + // 0007 + code.Make(code.OpJump, 13), + // 0010 + code.Make(code.OpConstant, 1), + // 0013 + code.Make(code.OpPop), + // 0014 + code.Make(code.OpConstant, 2), + // 0017 + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestGlobalLetStatements(t *testing.T) { + tests := []compilerTestCase{ + { + input: ` + let one = 1; + let two = 2; + `, + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpSetGlobal, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpSetGlobal, 1), + }, + }, + { + input: ` + let one = 1; + one; + `, + expectedConstants: []interface{}{1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpSetGlobal, 0), + code.Make(code.OpGetGlobal, 0), + code.Make(code.OpPop), + }, + }, + { + input: ` + let one = 1; + let two = one; + two; + `, + expectedConstants: []interface{}{1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpSetGlobal, 0), + code.Make(code.OpGetGlobal, 0), + code.Make(code.OpSetGlobal, 1), + code.Make(code.OpGetGlobal, 1), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestFunctions(t *testing.T) { + tests := []compilerTestCase{ + { + input: `fn() { return 5 + 10 }`, + expectedConstants: []interface{}{ + 5, + 10, + []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpAdd), + code.Make(code.OpReturnValue), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpClosure, 2, 0), + code.Make(code.OpPop), + }, + }, + { + input: `fn() { 5 + 10 }`, + expectedConstants: []interface{}{ + 5, + 10, + []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpAdd), + code.Make(code.OpReturnValue), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpClosure, 2, 0), + code.Make(code.OpPop), + }, + }, + { + input: `fn() { 1; 2 }`, + expectedConstants: []interface{}{ + 1, + 2, + []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpPop), + code.Make(code.OpConstant, 1), + code.Make(code.OpReturnValue), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpClosure, 2, 0), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +type compilerTestCase struct { + input string + expectedConstants []interface{} + expectedInstructions []code.Instructions +} + +func runCompilerTests(t *testing.T, tests []compilerTestCase) { + t.Helper() + + for _, tt := range tests { + program := parse(tt.input) + + compiler := New() + err := compiler.Compile(program) + if err != nil { + t.Fatalf("compiler error: %s", err) + } + + bytecode := compiler.Bytecode() + + err = testInstructions(tt.expectedInstructions, bytecode.Instructions) + if err != nil { + t.Fatalf("testInstructions failed: %s", err) + } + + err = testConstants(t, tt.expectedConstants, bytecode.Constants) + if err != nil { + t.Fatalf("testConstants failed: %s", err) + } + } +} + +func TestStringExpressions(t *testing.T) { + tests := []compilerTestCase{ + { + input: `"monkey"`, + expectedConstants: []interface{}{"monkey"}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpPop), + }, + }, + { + input: `"mon" + "key"`, + expectedConstants: []interface{}{"mon", "key"}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpAdd), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestArrayLiterals(t *testing.T) { + tests := []compilerTestCase{ + { + input: "[]", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpArray, 0), + code.Make(code.OpPop), + }, + }, + { + input: "[1, 2, 3]", + expectedConstants: []interface{}{1, 2, 3}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpConstant, 2), + code.Make(code.OpArray, 3), + code.Make(code.OpPop), + }, + }, + { + input: "[1 + 2, 3 - 4, 5 * 6]", + expectedConstants: []interface{}{1, 2, 3, 4, 5, 6}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpAdd), + code.Make(code.OpConstant, 2), + code.Make(code.OpConstant, 3), + code.Make(code.OpSub), + code.Make(code.OpConstant, 4), + code.Make(code.OpConstant, 5), + code.Make(code.OpMul), + code.Make(code.OpArray, 3), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestHashLiterals(t *testing.T) { + tests := []compilerTestCase{ + { + input: "{}", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpHash, 0), + code.Make(code.OpPop), + }, + }, + { + input: "{1: 2, 3: 4, 5: 6}", + expectedConstants: []interface{}{1, 2, 3, 4, 5, 6}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpConstant, 2), + code.Make(code.OpConstant, 3), + code.Make(code.OpConstant, 4), + code.Make(code.OpConstant, 5), + code.Make(code.OpHash, 6), + code.Make(code.OpPop), + }, + }, + { + input: "{1: 2 + 3, 4: 5 * 6}", + expectedConstants: []interface{}{1, 2, 3, 4, 5, 6}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpConstant, 2), + code.Make(code.OpAdd), + code.Make(code.OpConstant, 3), + code.Make(code.OpConstant, 4), + code.Make(code.OpConstant, 5), + code.Make(code.OpMul), + code.Make(code.OpHash, 4), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestIndexExpressions(t *testing.T) { + tests := []compilerTestCase{ + { + input: "[1, 2, 3][1 + 1]", + expectedConstants: []interface{}{1, 2, 3, 1, 1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpConstant, 2), + code.Make(code.OpArray, 3), + code.Make(code.OpConstant, 3), + code.Make(code.OpConstant, 4), + code.Make(code.OpAdd), + code.Make(code.OpIndex), + code.Make(code.OpPop), + }, + }, + { + input: "{1: 2}[2 - 1]", + expectedConstants: []interface{}{1, 2, 2, 1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpHash, 2), + code.Make(code.OpConstant, 2), + code.Make(code.OpConstant, 3), + code.Make(code.OpSub), + code.Make(code.OpIndex), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestFunctionsWithoutReturnValue(t *testing.T) { + tests := []compilerTestCase{ + { + input: `fn() { }`, + expectedConstants: []interface{}{ + []code.Instructions{ + code.Make(code.OpReturn), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpClosure, 0, 0), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestFunctionCalls(t *testing.T) { + tests := []compilerTestCase{ + { + input: `fn() { 24 }();`, + expectedConstants: []interface{}{ + 24, + []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpReturnValue), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpClosure, 1, 0), + code.Make(code.OpCall, 0), + code.Make(code.OpPop), + }, + }, + { + input: ` + let noArg = fn() { 24 }; + noArg(); + `, + expectedConstants: []interface{}{ + 24, + []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpReturnValue), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpClosure, 1, 0), + code.Make(code.OpSetGlobal, 0), + code.Make(code.OpGetGlobal, 0), + code.Make(code.OpCall, 0), + code.Make(code.OpPop), + }, + }, + { + input: ` + let oneArg = fn(a) { a }; + oneArg(24); + `, + expectedConstants: []interface{}{ + []code.Instructions{ + code.Make(code.OpGetLocal, 0), + code.Make(code.OpReturnValue), + }, + 24, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpClosure, 0, 0), + code.Make(code.OpSetGlobal, 0), + code.Make(code.OpGetGlobal, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpCall, 1), + code.Make(code.OpPop), + }, + }, + { + input: ` + let manyArg = fn(a, b, c) { a; b; c }; + manyArg(24, 25, 26); + `, + expectedConstants: []interface{}{ + []code.Instructions{ + code.Make(code.OpGetLocal, 0), + code.Make(code.OpPop), + code.Make(code.OpGetLocal, 1), + code.Make(code.OpPop), + code.Make(code.OpGetLocal, 2), + code.Make(code.OpReturnValue), + }, + 24, + 25, + 26, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpClosure, 0, 0), + code.Make(code.OpSetGlobal, 0), + code.Make(code.OpGetGlobal, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpConstant, 2), + code.Make(code.OpConstant, 3), + code.Make(code.OpCall, 3), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestLetStatementScopes(t *testing.T) { + tests := []compilerTestCase{ + { + input: ` + let num = 55; + fn() { num } + `, + expectedConstants: []interface{}{ + 55, + []code.Instructions{ + code.Make(code.OpGetGlobal, 0), + code.Make(code.OpReturnValue), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpSetGlobal, 0), + code.Make(code.OpClosure, 1, 0), + code.Make(code.OpPop), + }, + }, + { + input: ` + fn() { + let num = 55; + num + } + `, + expectedConstants: []interface{}{ + 55, + []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpSetLocal, 0), + code.Make(code.OpGetLocal, 0), + code.Make(code.OpReturnValue), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpClosure, 1, 0), + code.Make(code.OpPop), + }, + }, + { + input: ` + fn() { + let a = 55; + let b = 77; + a + b + } + `, + expectedConstants: []interface{}{ + 55, + 77, + []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpSetLocal, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpSetLocal, 1), + code.Make(code.OpGetLocal, 0), + code.Make(code.OpGetLocal, 1), + code.Make(code.OpAdd), + code.Make(code.OpReturnValue), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpClosure, 2, 0), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestBuiltins(t *testing.T) { + tests := []compilerTestCase{ + { + input: ` + len([]); + push([], 1); + `, + expectedConstants: []interface{}{1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpGetBuiltin, 0), + code.Make(code.OpArray, 0), + code.Make(code.OpCall, 1), + code.Make(code.OpPop), + code.Make(code.OpGetBuiltin, 5), + code.Make(code.OpArray, 0), + code.Make(code.OpConstant, 0), + code.Make(code.OpCall, 2), + code.Make(code.OpPop), + }, + }, + { + input: `fn() { len([]) }`, + expectedConstants: []interface{}{ + []code.Instructions{ + code.Make(code.OpGetBuiltin, 0), + code.Make(code.OpArray, 0), + code.Make(code.OpCall, 1), + code.Make(code.OpReturnValue), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpClosure, 0, 0), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestClosures(t *testing.T) { + tests := []compilerTestCase{ + { + input: ` + fn(a) { + fn(b) { + a + b + } + } + `, + expectedConstants: []interface{}{ + []code.Instructions{ + code.Make(code.OpGetFree, 0), + code.Make(code.OpGetLocal, 0), + code.Make(code.OpAdd), + code.Make(code.OpReturnValue), + }, + []code.Instructions{ + code.Make(code.OpGetLocal, 0), + code.Make(code.OpClosure, 0, 1), + code.Make(code.OpReturnValue), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpClosure, 1, 0), + code.Make(code.OpPop), + }, + }, + { + input: ` + fn(a) { + fn(b) { + fn(c) { + a + b + c + } + } + }; + `, + expectedConstants: []interface{}{ + []code.Instructions{ + code.Make(code.OpGetFree, 0), + code.Make(code.OpGetFree, 1), + code.Make(code.OpAdd), + code.Make(code.OpGetLocal, 0), + code.Make(code.OpAdd), + code.Make(code.OpReturnValue), + }, + []code.Instructions{ + code.Make(code.OpGetFree, 0), + code.Make(code.OpGetLocal, 0), + code.Make(code.OpClosure, 0, 2), + code.Make(code.OpReturnValue), + }, + []code.Instructions{ + code.Make(code.OpGetLocal, 0), + code.Make(code.OpClosure, 1, 1), + code.Make(code.OpReturnValue), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpClosure, 2, 0), + code.Make(code.OpPop), + }, + }, + { + input: ` + let global = 55; + + fn() { + let a = 66; + + fn() { + let b = 77; + + fn() { + let c = 88; + + global + a + b + c; + } + } + } + `, + expectedConstants: []interface{}{ + 55, + 66, + 77, + 88, + []code.Instructions{ + code.Make(code.OpConstant, 3), + code.Make(code.OpSetLocal, 0), + code.Make(code.OpGetGlobal, 0), + code.Make(code.OpGetFree, 0), + code.Make(code.OpAdd), + code.Make(code.OpGetFree, 1), + code.Make(code.OpAdd), + code.Make(code.OpGetLocal, 0), + code.Make(code.OpAdd), + code.Make(code.OpReturnValue), + }, + []code.Instructions{ + code.Make(code.OpConstant, 2), + code.Make(code.OpSetLocal, 0), + code.Make(code.OpGetFree, 0), + code.Make(code.OpGetLocal, 0), + code.Make(code.OpClosure, 4, 2), + code.Make(code.OpReturnValue), + }, + []code.Instructions{ + code.Make(code.OpConstant, 1), + code.Make(code.OpSetLocal, 0), + code.Make(code.OpGetLocal, 0), + code.Make(code.OpClosure, 5, 1), + code.Make(code.OpReturnValue), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpSetGlobal, 0), + code.Make(code.OpClosure, 6, 0), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestRecursiveFunctions(t *testing.T) { + tests := []compilerTestCase{ + { + input: ` + let countDown = fn(x) { countDown(x - 1); }; + countDown(1); + `, + expectedConstants: []interface{}{ + 1, + []code.Instructions{ + code.Make(code.OpCurrentClosure), + code.Make(code.OpGetLocal, 0), + code.Make(code.OpConstant, 0), + code.Make(code.OpSub), + code.Make(code.OpCall, 1), + code.Make(code.OpReturnValue), + }, + 1, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpClosure, 1, 0), + code.Make(code.OpSetGlobal, 0), + code.Make(code.OpGetGlobal, 0), + code.Make(code.OpConstant, 2), + code.Make(code.OpCall, 1), + code.Make(code.OpPop), + }, + }, + { + input: ` + let wrapper = fn() { + let countDown = fn(x) { countDown(x - 1); }; + countDown(1); + }; + wrapper(); + `, + expectedConstants: []interface{}{ + 1, + []code.Instructions{ + code.Make(code.OpCurrentClosure), + code.Make(code.OpGetLocal, 0), + code.Make(code.OpConstant, 0), + code.Make(code.OpSub), + code.Make(code.OpCall, 1), + code.Make(code.OpReturnValue), + }, + 1, + []code.Instructions{ + code.Make(code.OpClosure, 1, 0), + code.Make(code.OpSetLocal, 0), + code.Make(code.OpGetLocal, 0), + code.Make(code.OpConstant, 2), + code.Make(code.OpCall, 1), + code.Make(code.OpReturnValue), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpClosure, 3, 0), + code.Make(code.OpSetGlobal, 0), + code.Make(code.OpGetGlobal, 0), + code.Make(code.OpCall, 0), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func parse(input string) *ast.Program { + l := lexer.New(input) + p := parser.New(l) + return p.ParseProgram() +} + +func testInstructions( + expected []code.Instructions, + actual code.Instructions, +) error { + concatted := concatInstructions(expected) + + if len(actual) != len(concatted) { + return fmt.Errorf("wrong instructions length.\nwant=%q\ngot =%q", + concatted, actual) + } + + for i, ins := range concatted { + if actual[i] != ins { + return fmt.Errorf("wrong instruction at %d.\nwant=%q\ngot =%q", + i, concatted, actual) + } + } + + return nil +} + +func concatInstructions(s []code.Instructions) code.Instructions { + out := code.Instructions{} + + for _, ins := range s { + out = append(out, ins...) + } + + return out +} + +func testConstants( + t *testing.T, + expected []interface{}, + actual []object.Object, +) error { + if len(expected) != len(actual) { + return fmt.Errorf("wrong number of constants. got=%d, want=%d", + len(actual), len(expected)) + } + + for i, constant := range expected { + switch constant := constant.(type) { + case string: + err := testStringObject(constant, actual[i]) + if err != nil { + return fmt.Errorf("constant %d - testStringObject failed: %s", + i, err) + } + case int: + err := testIntegerObject(int64(constant), actual[i]) + if err != nil { + return fmt.Errorf("constant %d - testIntegerObject failed: %s", + i, err) + } + case []code.Instructions: + fn, ok := actual[i].(*object.CompiledFunction) + if !ok { + return fmt.Errorf("constant %d - not a function: %T", + i, actual[i]) + } + + err := testInstructions(constant, fn.Instructions) + if err != nil { + return fmt.Errorf("constant %d - testInstructions failed: %s", + i, err) + } + } + } + + return nil +} + +func testIntegerObject(expected int64, actual object.Object) error { + result, ok := actual.(*object.Integer) + if !ok { + return fmt.Errorf("object is not Integer. got=%T (%+v)", + actual, actual) + } + + if result.Value != expected { + return fmt.Errorf("object has wrong value. got=%d, want=%d", + result.Value, expected) + } + + return nil +} + +func testStringObject(expected string, actual object.Object) error { + result, ok := actual.(*object.String) + if !ok { + return fmt.Errorf("object is not String. got=%T (%+v)", + actual, actual) + } + + if result.Value != expected { + return fmt.Errorf("object has wrong value. got=%q, want=%q", + result.Value, expected) + } + + return nil +} diff --git a/wcig_code_1_2/09/src/monkey/compiler/symbol_table.go b/wcig_code_1_2/09/src/monkey/compiler/symbol_table.go new file mode 100644 index 0000000..10f4d03 --- /dev/null +++ b/wcig_code_1_2/09/src/monkey/compiler/symbol_table.go @@ -0,0 +1,91 @@ +package compiler + +type SymbolScope string + +const ( + LocalScope SymbolScope = "LOCAL" + GlobalScope SymbolScope = "GLOBAL" + BuiltinScope SymbolScope = "BUILTIN" + FreeScope SymbolScope = "FREE" + FunctionScope SymbolScope = "FUNCTION" +) + +type Symbol struct { + Name string + Scope SymbolScope + Index int +} + +type SymbolTable struct { + Outer *SymbolTable + + store map[string]Symbol + numDefinitions int + + FreeSymbols []Symbol +} + +func NewEnclosedSymbolTable(outer *SymbolTable) *SymbolTable { + s := NewSymbolTable() + s.Outer = outer + return s +} + +func NewSymbolTable() *SymbolTable { + s := make(map[string]Symbol) + free := []Symbol{} + return &SymbolTable{store: s, FreeSymbols: free} +} + +func (s *SymbolTable) Define(name string) Symbol { + symbol := Symbol{Name: name, Index: s.numDefinitions} + if s.Outer == nil { + symbol.Scope = GlobalScope + } else { + symbol.Scope = LocalScope + } + + s.store[name] = symbol + s.numDefinitions++ + return symbol +} + +func (s *SymbolTable) Resolve(name string) (Symbol, bool) { + obj, ok := s.store[name] + if !ok && s.Outer != nil { + obj, ok = s.Outer.Resolve(name) + if !ok { + return obj, ok + } + + if obj.Scope == GlobalScope || obj.Scope == BuiltinScope { + return obj, ok + } + + free := s.defineFree(obj) + return free, true + } + return obj, ok +} + +func (s *SymbolTable) DefineBuiltin(index int, name string) Symbol { + symbol := Symbol{Name: name, Index: index, Scope: BuiltinScope} + s.store[name] = symbol + return symbol +} + +func (s *SymbolTable) DefineFunctionName(name string) Symbol { + symbol := Symbol{Name: name, Index: 0, Scope: FunctionScope} + s.store[name] = symbol + return symbol +} + +func (s *SymbolTable) defineFree(original Symbol) Symbol { + s.FreeSymbols = append(s.FreeSymbols, original) + + symbol := Symbol{Name: original.Name, Index: len(s.FreeSymbols) - 1} + symbol.Scope = FreeScope + + s.store[original.Name] = symbol + return symbol +} diff --git a/wcig_code_1_2/09/src/monkey/compiler/symbol_table_test.go b/wcig_code_1_2/09/src/monkey/compiler/symbol_table_test.go new file mode 100644 index 0000000..b66f2f7 --- /dev/null +++ b/wcig_code_1_2/09/src/monkey/compiler/symbol_table_test.go @@ -0,0 +1,337 @@ +package compiler + +import "testing" + +func TestDefine(t *testing.T) { + expected := map[string]Symbol{ + "a": Symbol{Name: "a", Scope: GlobalScope, Index: 0}, + "b": Symbol{Name: "b", Scope: GlobalScope, Index: 1}, + "c": Symbol{Name: "c", Scope: LocalScope, Index: 0}, + "d": Symbol{Name: "d", Scope: LocalScope, Index: 1}, + "e": Symbol{Name: "e", Scope: LocalScope, Index: 0}, + "f": Symbol{Name: "f", Scope: LocalScope, Index: 1}, + } + + global := NewSymbolTable() + + a := global.Define("a") + if a != expected["a"] { + t.Errorf("expected a=%+v, got=%+v", expected["a"], a) + } + + b := global.Define("b") + if b != expected["b"] { + t.Errorf("expected b=%+v, got=%+v", expected["b"], b) + } + + firstLocal := NewEnclosedSymbolTable(global) + + c := firstLocal.Define("c") + if c != expected["c"] { + t.Errorf("expected c=%+v, got=%+v", expected["c"], c) + } + + d := firstLocal.Define("d") + if d != expected["d"] { + t.Errorf("expected d=%+v, got=%+v", expected["d"], d) + } + + secondLocal := NewEnclosedSymbolTable(firstLocal) + + e := secondLocal.Define("e") + if e != expected["e"] { + t.Errorf("expected e=%+v, got=%+v", expected["e"], e) + } + + f := secondLocal.Define("f") + if f != expected["f"] { + t.Errorf("expected f=%+v, got=%+v", expected["f"], f) + } +} + +func TestResolveGlobal(t *testing.T) { + global := NewSymbolTable() + global.Define("a") + global.Define("b") + + expected := []Symbol{ + Symbol{Name: "a", Scope: GlobalScope, Index: 0}, + Symbol{Name: "b", Scope: GlobalScope, Index: 1}, + } + + for _, sym := range expected { + result, ok := global.Resolve(sym.Name) + if !ok { + t.Errorf("name %s not resolvable", sym.Name) + continue + } + if result != sym { + t.Errorf("expected %s to resolve to %+v, got=%+v", + sym.Name, sym, result) + } + } +} + +func TestResolveLocal(t *testing.T) { + global := NewSymbolTable() + global.Define("a") + global.Define("b") + + local := NewEnclosedSymbolTable(global) + local.Define("c") + local.Define("d") + + expected := []Symbol{ + Symbol{Name: "a", Scope: GlobalScope, Index: 0}, + Symbol{Name: "b", Scope: GlobalScope, Index: 1}, + Symbol{Name: "c", Scope: LocalScope, Index: 0}, + Symbol{Name: "d", Scope: LocalScope, Index: 1}, + } + + for _, sym := range expected { + result, ok := local.Resolve(sym.Name) + if !ok { + t.Errorf("name %s not resolvable", sym.Name) + continue + } + if result != sym { + t.Errorf("expected %s to resolve to %+v, got=%+v", + sym.Name, sym, result) + } + } +} + +func TestResolveNestedLocal(t *testing.T) { + global := NewSymbolTable() + global.Define("a") + global.Define("b") + + firstLocal := NewEnclosedSymbolTable(global) + firstLocal.Define("c") + firstLocal.Define("d") + + secondLocal := NewEnclosedSymbolTable(firstLocal) + secondLocal.Define("e") + secondLocal.Define("f") + + tests := []struct { + table *SymbolTable + expectedSymbols []Symbol + }{ + { + firstLocal, + []Symbol{ + Symbol{Name: "a", Scope: GlobalScope, Index: 0}, + Symbol{Name: "b", Scope: GlobalScope, Index: 1}, + Symbol{Name: "c", Scope: LocalScope, Index: 0}, + Symbol{Name: "d", Scope: LocalScope, Index: 1}, + }, + }, + { + secondLocal, + []Symbol{ + Symbol{Name: "a", Scope: GlobalScope, Index: 0}, + Symbol{Name: "b", Scope: GlobalScope, Index: 1}, + Symbol{Name: "e", Scope: LocalScope, Index: 0}, + Symbol{Name: "f", Scope: LocalScope, Index: 1}, + }, + }, + } + + for _, tt := range tests { + for _, sym := range tt.expectedSymbols { + result, ok := tt.table.Resolve(sym.Name) + if !ok { + t.Errorf("name %s not resolvable", sym.Name) + continue + } + if result != sym { + t.Errorf("expected %s to resolve to %+v, got=%+v", + sym.Name, sym, result) + } + } + } +} + +func TestDefineResolveBuiltins(t *testing.T) { + global := NewSymbolTable() + firstLocal := NewEnclosedSymbolTable(global) + secondLocal := NewEnclosedSymbolTable(firstLocal) + + expected := []Symbol{ + Symbol{Name: "a", Scope: BuiltinScope, Index: 0}, + Symbol{Name: "c", Scope: BuiltinScope, Index: 1}, + Symbol{Name: "e", Scope: BuiltinScope, Index: 2}, + Symbol{Name: "f", Scope: BuiltinScope, Index: 3}, + } + + for i, v := range expected { + global.DefineBuiltin(i, v.Name) + } + + for _, table := range []*SymbolTable{global, firstLocal, secondLocal} { + for _, sym := range expected { + result, ok := table.Resolve(sym.Name) + if !ok { + t.Errorf("name %s not resolvable", sym.Name) + continue + } + if result != sym { + t.Errorf("expected %s to resolve to %+v, got=%+v", + sym.Name, sym, result) + } + } + } +} + +func TestResolveFree(t *testing.T) { + global := NewSymbolTable() + global.Define("a") + global.Define("b") + + firstLocal := NewEnclosedSymbolTable(global) + firstLocal.Define("c") + firstLocal.Define("d") + + secondLocal := NewEnclosedSymbolTable(firstLocal) + secondLocal.Define("e") + secondLocal.Define("f") + + tests := []struct { + table *SymbolTable + expectedSymbols []Symbol + expectedFreeSymbols []Symbol + }{ + { + firstLocal, + []Symbol{ + Symbol{Name: "a", Scope: GlobalScope, Index: 0}, + Symbol{Name: "b", Scope: GlobalScope, Index: 1}, + Symbol{Name: "c", Scope: LocalScope, Index: 0}, + Symbol{Name: "d", Scope: LocalScope, Index: 1}, + }, + []Symbol{}, + }, + { + secondLocal, + []Symbol{ + Symbol{Name: "a", Scope: GlobalScope, Index: 0}, + Symbol{Name: "b", Scope: GlobalScope, Index: 1}, + Symbol{Name: "c", Scope: FreeScope, Index: 0}, + Symbol{Name: "d", Scope: FreeScope, Index: 1}, + Symbol{Name: "e", Scope: LocalScope, Index: 0}, + Symbol{Name: "f", Scope: LocalScope, Index: 1}, + }, + []Symbol{ + Symbol{Name: "c", Scope: LocalScope, Index: 0}, + Symbol{Name: "d", Scope: LocalScope, Index: 1}, + }, + }, + } + + for _, tt := range tests { + for _, sym := range tt.expectedSymbols { + result, ok := tt.table.Resolve(sym.Name) + if !ok { + t.Errorf("name %s not resolvable", sym.Name) + continue + } + if result != sym { + t.Errorf("expected %s to resolve to %+v, got=%+v", + sym.Name, sym, result) + } + } + + if len(tt.table.FreeSymbols) != len(tt.expectedFreeSymbols) { + t.Errorf("wrong number of free symbols. got=%d, want=%d", + len(tt.table.FreeSymbols), len(tt.expectedFreeSymbols)) + continue + } + + for i, sym := range tt.expectedFreeSymbols { + result := tt.table.FreeSymbols[i] + if result != sym { + t.Errorf("wrong free symbol. got=%+v, want=%+v", + result, sym) + } + } + } +} + +func TestResolveUnresolvableFree(t *testing.T) { + global := NewSymbolTable() + global.Define("a") + + firstLocal := NewEnclosedSymbolTable(global) + firstLocal.Define("c") + + secondLocal := NewEnclosedSymbolTable(firstLocal) + secondLocal.Define("e") + secondLocal.Define("f") + + expected := []Symbol{ + Symbol{Name: "a", Scope: GlobalScope, Index: 0}, + Symbol{Name: "c", Scope: FreeScope, Index: 0}, + Symbol{Name: "e", Scope: LocalScope, Index: 0}, + Symbol{Name: "f", Scope: LocalScope, Index: 1}, + } + + for _, sym := range expected { + result, ok := secondLocal.Resolve(sym.Name) + if !ok { + t.Errorf("name %s not resolvable", sym.Name) + continue + } + if result != sym { + t.Errorf("expected %s to resolve to %+v, got=%+v", + sym.Name, sym, result) + } + } + + expectedUnresolvable := []string{ + "b", + "d", + } + + for _, name := range expectedUnresolvable { + _, ok := secondLocal.Resolve(name) + if ok { + t.Errorf("name %s resolved, but was expected not to", name) + } + } +} + +func TestDefineAndResolveFunctionName(t *testing.T) { + global := NewSymbolTable() + global.DefineFunctionName("a") + + expected := Symbol{Name: "a", Scope: FunctionScope, Index: 0} + + result, ok := global.Resolve(expected.Name) + if !ok { + t.Fatalf("function name %s not resolvable", expected.Name) + } + + if result != expected { + t.Errorf("expected %s to resolve to %+v, got=%+v", + expected.Name, expected, result) + } +} + +func TestShadowingFunctionName(t *testing.T) { + global := NewSymbolTable() + global.DefineFunctionName("a") + global.Define("a") + + expected := Symbol{Name: "a", Scope: GlobalScope, Index: 0} + + result, ok := global.Resolve(expected.Name) + if !ok { + t.Fatalf("function name %s not resolvable", expected.Name) + } + + if result != expected { + t.Errorf("expected %s to resolve to %+v, got=%+v", + expected.Name, expected, result) + } +} diff --git a/wcig_code_1_2/09/src/monkey/evaluator/builtins.go b/wcig_code_1_2/09/src/monkey/evaluator/builtins.go new file mode 100644 index 0000000..088ef5d --- /dev/null +++ b/wcig_code_1_2/09/src/monkey/evaluator/builtins.go @@ -0,0 +1,14 @@ +package evaluator + +import ( + "monkey/object" +) + +var builtins = map[string]*object.Builtin{ + "len": object.GetBuiltinByName("len"), + "puts": object.GetBuiltinByName("puts"), + "first": object.GetBuiltinByName("first"), + "last": object.GetBuiltinByName("last"), + "rest": object.GetBuiltinByName("rest"), + "push": object.GetBuiltinByName("push"), +} diff --git a/wcig_code_1_2/09/src/monkey/evaluator/evaluator.go b/wcig_code_1_2/09/src/monkey/evaluator/evaluator.go new file mode 100644 index 0000000..84c9011 --- /dev/null +++ b/wcig_code_1_2/09/src/monkey/evaluator/evaluator.go @@ -0,0 +1,445 @@ +package evaluator + +import ( + "fmt" + "monkey/ast" + "monkey/object" +) + +var ( + NULL = &object.Null{} + TRUE = &object.Boolean{Value: true} + FALSE = &object.Boolean{Value: false} +) + +func Eval(node ast.Node, env *object.Environment) object.Object { + switch node := node.(type) { + + // Statements + case *ast.Program: + return evalProgram(node, env) + + case *ast.BlockStatement: + return evalBlockStatement(node, env) + + case *ast.ExpressionStatement: + return Eval(node.Expression, env) + + case *ast.ReturnStatement: + val := Eval(node.ReturnValue, env) + if isError(val) { + return val + } + return &object.ReturnValue{Value: val} + + case *ast.LetStatement: + val := Eval(node.Value, env) + if isError(val) { + return val + } + env.Set(node.Name.Value, val) + + // Expressions + case *ast.IntegerLiteral: + return &object.Integer{Value: node.Value} + + case *ast.StringLiteral: + return &object.String{Value: node.Value} + + case *ast.Boolean: + return nativeBoolToBooleanObject(node.Value) + + case *ast.PrefixExpression: + right := Eval(node.Right, env) + if isError(right) { + return right + } + return evalPrefixExpression(node.Operator, right) + + case *ast.InfixExpression: + left := Eval(node.Left, env) + if isError(left) { + return left + } + + right := Eval(node.Right, env) + if isError(right) { + return right + } + + return evalInfixExpression(node.Operator, left, right) + + case *ast.IfExpression: + return evalIfExpression(node, env) + + case *ast.Identifier: + return evalIdentifier(node, env) + + case *ast.FunctionLiteral: + params := node.Parameters + body := node.Body + return &object.Function{Parameters: params, Env: env, Body: body} + + case *ast.CallExpression: + function := Eval(node.Function, env) + if isError(function) { + return function + } + + args := evalExpressions(node.Arguments, env) + if len(args) == 1 && isError(args[0]) { + return args[0] + } + + return applyFunction(function, args) + + case *ast.ArrayLiteral: + elements := evalExpressions(node.Elements, env) + if len(elements) == 1 && isError(elements[0]) { + return elements[0] + } + return &object.Array{Elements: elements} + + case *ast.IndexExpression: + left := Eval(node.Left, env) + if isError(left) { + return left + } + index := Eval(node.Index, env) + if isError(index) { + return index + } + return evalIndexExpression(left, index) + + case *ast.HashLiteral: + return evalHashLiteral(node, env) + + } + + return nil +} + +func evalProgram(program *ast.Program, env *object.Environment) object.Object { + var result object.Object + + for _, statement := range program.Statements { + result = Eval(statement, env) + + switch result := result.(type) { + case *object.ReturnValue: + return result.Value + case *object.Error: + return result + } + } + + return result +} + +func evalBlockStatement( + block *ast.BlockStatement, + env *object.Environment, +) object.Object { + var result object.Object + + for _, statement := range block.Statements { + result = Eval(statement, env) + + if result != nil { + rt := result.Type() + if rt == object.RETURN_VALUE_OBJ || rt == object.ERROR_OBJ { + return result + } + } + } + + return result +} + +func nativeBoolToBooleanObject(input bool) *object.Boolean { + if input { + return TRUE + } + return FALSE +} + +func evalPrefixExpression(operator string, right object.Object) object.Object { + switch operator { + case "!": + return evalBangOperatorExpression(right) + case "-": + return evalMinusPrefixOperatorExpression(right) + default: + return newError("unknown operator: %s%s", operator, right.Type()) + } +} + +func evalInfixExpression( + operator string, + left, right object.Object, +) object.Object { + switch { + case left.Type() == object.INTEGER_OBJ && right.Type() == object.INTEGER_OBJ: + return evalIntegerInfixExpression(operator, left, right) + case left.Type() == object.STRING_OBJ && right.Type() == object.STRING_OBJ: + return evalStringInfixExpression(operator, left, right) + case operator == "==": + return nativeBoolToBooleanObject(left == right) + case operator == "!=": + return nativeBoolToBooleanObject(left != right) + case left.Type() != right.Type(): + return newError("type mismatch: %s %s %s", + left.Type(), operator, right.Type()) + default: + return newError("unknown operator: %s %s %s", + left.Type(), operator, right.Type()) + } +} + +func evalBangOperatorExpression(right object.Object) object.Object { + switch right { + case TRUE: + return FALSE + case FALSE: + return TRUE + case NULL: + return TRUE + default: + return FALSE + } +} + +func evalMinusPrefixOperatorExpression(right object.Object) object.Object { + if right.Type() != object.INTEGER_OBJ { + return newError("unknown operator: -%s", right.Type()) + } + + value := right.(*object.Integer).Value + return &object.Integer{Value: -value} +} + +func evalIntegerInfixExpression( + operator string, + left, right object.Object, +) object.Object { + leftVal := left.(*object.Integer).Value + rightVal := right.(*object.Integer).Value + + switch operator { + case "+": + return &object.Integer{Value: leftVal + rightVal} + case "-": + return &object.Integer{Value: leftVal - rightVal} + case "*": + return &object.Integer{Value: leftVal * rightVal} + case "/": + return &object.Integer{Value: leftVal / rightVal} + case "<": + return nativeBoolToBooleanObject(leftVal < rightVal) + case ">": + return nativeBoolToBooleanObject(leftVal > rightVal) + case "==": + return nativeBoolToBooleanObject(leftVal == rightVal) + case "!=": + return nativeBoolToBooleanObject(leftVal != rightVal) + default: + return newError("unknown operator: %s %s %s", + left.Type(), operator, right.Type()) + } +} + +func evalStringInfixExpression( + operator string, + left, right object.Object, +) object.Object { + if operator != "+" { + return newError("unknown operator: %s %s %s", + left.Type(), operator, right.Type()) + } + + leftVal := left.(*object.String).Value + rightVal := right.(*object.String).Value + return &object.String{Value: leftVal + rightVal} +} + +func evalIfExpression( + ie *ast.IfExpression, + env *object.Environment, +) object.Object { + condition := Eval(ie.Condition, env) + if isError(condition) { + return condition + } + + if isTruthy(condition) { + return Eval(ie.Consequence, env) + } else if ie.Alternative != nil { + return Eval(ie.Alternative, env) + } else { + return NULL + } +} + +func evalIdentifier( + node *ast.Identifier, + env *object.Environment, +) object.Object { + if val, ok := env.Get(node.Value); ok { + return val + } + + if builtin, ok := builtins[node.Value]; ok { + return builtin + } + + return newError("identifier not found: " + node.Value) +} + +func isTruthy(obj object.Object) bool { + switch obj { + case NULL: + return false + case TRUE: + return true + case FALSE: + return false + default: + return true + } +} + +func newError(format string, a ...interface{}) *object.Error { + return &object.Error{Message: fmt.Sprintf(format, a...)} +} + +func isError(obj object.Object) bool { + if obj != nil { + return obj.Type() == object.ERROR_OBJ + } + return false +} + +func evalExpressions( + exps []ast.Expression, + env *object.Environment, +) []object.Object { + var result []object.Object + + for _, e := range exps { + evaluated := Eval(e, env) + if isError(evaluated) { + return []object.Object{evaluated} + } + result = append(result, evaluated) + } + + return result +} + +func applyFunction(fn object.Object, args []object.Object) object.Object { + switch fn := fn.(type) { + + case *object.Function: + extendedEnv := extendFunctionEnv(fn, args) + evaluated := Eval(fn.Body, extendedEnv) + return unwrapReturnValue(evaluated) + + case *object.Builtin: + if result := fn.Fn(args...); result != nil { + return result + } + return NULL + + default: + return newError("not a function: %s", fn.Type()) + } +} + +func extendFunctionEnv( + fn *object.Function, + args []object.Object, +) *object.Environment { + env := object.NewEnclosedEnvironment(fn.Env) + + for paramIdx, param := range fn.Parameters { + env.Set(param.Value, args[paramIdx]) + } + + return env +} + +func unwrapReturnValue(obj object.Object) object.Object { + if returnValue, ok := obj.(*object.ReturnValue); ok { + return returnValue.Value + } + + return obj +} + +func evalIndexExpression(left, index object.Object) object.Object { + switch { + case left.Type() == object.ARRAY_OBJ && index.Type() == object.INTEGER_OBJ: + return evalArrayIndexExpression(left, index) + case left.Type() == object.HASH_OBJ: + return evalHashIndexExpression(left, index) + default: + return newError("index operator not supported: %s", left.Type()) + } +} + +func evalArrayIndexExpression(array, index object.Object) object.Object { + arrayObject := array.(*object.Array) + idx := index.(*object.Integer).Value + max := int64(len(arrayObject.Elements) - 1) + + if idx < 0 || idx > max { + return NULL + } + + return arrayObject.Elements[idx] +} + +func evalHashLiteral( + node *ast.HashLiteral, + env *object.Environment, +) object.Object { + pairs := make(map[object.HashKey]object.HashPair) + + for keyNode, valueNode := range node.Pairs { + key := Eval(keyNode, env) + if isError(key) { + return key + } + + hashKey, ok := key.(object.Hashable) + if !ok { + return newError("unusable as hash key: %s", key.Type()) + } + + value := Eval(valueNode, env) + if isError(value) { + return value + } + + hashed := hashKey.HashKey() + pairs[hashed] = object.HashPair{Key: key, Value: value} + } + + return &object.Hash{Pairs: pairs} +} + +func evalHashIndexExpression(hash, index object.Object) object.Object { + hashObject := hash.(*object.Hash) + + key, ok := index.(object.Hashable) + if !ok { + return newError("unusable as hash key: %s", index.Type()) + } + + pair, ok := hashObject.Pairs[key.HashKey()] + if !ok { + return NULL + } + + return pair.Value +} diff --git a/wcig_code_1_2/09/src/monkey/evaluator/evaluator_test.go b/wcig_code_1_2/09/src/monkey/evaluator/evaluator_test.go new file mode 100644 index 0000000..2304e6f --- /dev/null +++ b/wcig_code_1_2/09/src/monkey/evaluator/evaluator_test.go @@ -0,0 +1,629 @@ +package evaluator + +import ( + "monkey/lexer" + "monkey/object" + "monkey/parser" + "testing" +) + +func TestEvalIntegerExpression(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"5", 5}, + {"10", 10}, + {"-5", -5}, + {"-10", -10}, + {"5 + 5 + 5 + 5 - 10", 10}, + {"2 * 2 * 2 * 2 * 2", 32}, + {"-50 + 100 + -50", 0}, + {"5 * 2 + 10", 20}, + {"5 + 2 * 10", 25}, + {"20 + 2 * -10", 0}, + {"50 / 2 * 2 + 10", 60}, + {"2 * (5 + 10)", 30}, + {"3 * 3 * 3 + 10", 37}, + {"3 * (3 * 3) + 10", 37}, + {"(5 + 10 * 2 + 15 / 3) * 2 + -10", 50}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testIntegerObject(t, evaluated, tt.expected) + } +} + +func TestEvalBooleanExpression(t *testing.T) { + tests := []struct { + input string + expected bool + }{ + {"true", true}, + {"false", false}, + {"1 < 2", true}, + {"1 > 2", false}, + {"1 < 1", false}, + {"1 > 1", false}, + {"1 == 1", true}, + {"1 != 1", false}, + {"1 == 2", false}, + {"1 != 2", true}, + {"true == true", true}, + {"false == false", true}, + {"true == false", false}, + {"true != false", true}, + {"false != true", true}, + {"(1 < 2) == true", true}, + {"(1 < 2) == false", false}, + {"(1 > 2) == true", false}, + {"(1 > 2) == false", true}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testBooleanObject(t, evaluated, tt.expected) + } +} + +func TestBangOperator(t *testing.T) { + tests := []struct { + input string + expected bool + }{ + {"!true", false}, + {"!false", true}, + {"!5", false}, + {"!!true", true}, + {"!!false", false}, + {"!!5", true}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testBooleanObject(t, evaluated, tt.expected) + } +} + +func TestIfElseExpressions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + {"if (true) { 10 }", 10}, + {"if (false) { 10 }", nil}, + {"if (1) { 10 }", 10}, + {"if (1 < 2) { 10 }", 10}, + {"if (1 > 2) { 10 }", nil}, + {"if (1 > 2) { 10 } else { 20 }", 20}, + {"if (1 < 2) { 10 } else { 20 }", 10}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + integer, ok := tt.expected.(int) + if ok { + testIntegerObject(t, evaluated, int64(integer)) + } else { + testNullObject(t, evaluated) + } + } +} + +func TestReturnStatements(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"return 10;", 10}, + {"return 10; 9;", 10}, + {"return 2 * 5; 9;", 10}, + {"9; return 2 * 5; 9;", 10}, + {"if (10 > 1) { return 10; }", 10}, + { + ` +if (10 > 1) { + if (10 > 1) { + return 10; + } + + return 1; +} +`, + 10, + }, + { + ` +let f = fn(x) { + return x; + x + 10; +}; +f(10);`, + 10, + }, + { + ` +let f = fn(x) { + let result = x + 10; + return result; + return 10; +}; +f(10);`, + 20, + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testIntegerObject(t, evaluated, tt.expected) + } +} + +func TestErrorHandling(t *testing.T) { + tests := []struct { + input string + expectedMessage string + }{ + { + "5 + true;", + "type mismatch: INTEGER + BOOLEAN", + }, + { + "5 + true; 5;", + "type mismatch: INTEGER + BOOLEAN", + }, + { + "-true", + "unknown operator: -BOOLEAN", + }, + { + "true + false;", + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + "true + false + true + false;", + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + "5; true + false; 5", + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + `"Hello" - "World"`, + "unknown operator: STRING - STRING", + }, + { + "if (10 > 1) { true + false; }", + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + ` +if (10 > 1) { + if (10 > 1) { + return true + false; + } + + return 1; +} +`, + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + "foobar", + "identifier not found: foobar", + }, + { + `{"name": "Monkey"}[fn(x) { x }];`, + "unusable as hash key: FUNCTION", + }, + { + `999[1]`, + "index operator not supported: INTEGER", + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + + errObj, ok := evaluated.(*object.Error) + if !ok { + t.Errorf("no error object returned. got=%T(%+v)", + evaluated, evaluated) + continue + } + + if errObj.Message != tt.expectedMessage { + t.Errorf("wrong error message. expected=%q, got=%q", + tt.expectedMessage, errObj.Message) + } + } +} + +func TestLetStatements(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"let a = 5; a;", 5}, + {"let a = 5 * 5; a;", 25}, + {"let a = 5; let b = a; b;", 5}, + {"let a = 5; let b = a; let c = a + b + 5; c;", 15}, + } + + for _, tt := range tests { + testIntegerObject(t, testEval(tt.input), tt.expected) + } +} + +func TestFunctionObject(t *testing.T) { + input := "fn(x) { x + 2; };" + + evaluated := testEval(input) + fn, ok := evaluated.(*object.Function) + if !ok { + t.Fatalf("object is not Function. got=%T (%+v)", evaluated, evaluated) + } + + if len(fn.Parameters) != 1 { + t.Fatalf("function has wrong parameters. Parameters=%+v", + fn.Parameters) + } + + if fn.Parameters[0].String() != "x" { + t.Fatalf("parameter is not 'x'. got=%q", fn.Parameters[0]) + } + + expectedBody := "(x + 2)" + + if fn.Body.String() != expectedBody { + t.Fatalf("body is not %q. got=%q", expectedBody, fn.Body.String()) + } +} + +func TestFunctionApplication(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"let identity = fn(x) { x; }; identity(5);", 5}, + {"let identity = fn(x) { return x; }; identity(5);", 5}, + {"let double = fn(x) { x * 2; }; double(5);", 10}, + {"let add = fn(x, y) { x + y; }; add(5, 5);", 10}, + {"let add = fn(x, y) { x + y; }; add(5 + 5, add(5, 5));", 20}, + {"fn(x) { x; }(5)", 5}, + } + + for _, tt := range tests { + testIntegerObject(t, testEval(tt.input), tt.expected) + } +} + +func TestEnclosingEnvironments(t *testing.T) { + input := ` +let first = 10; +let second = 10; +let third = 10; + +let ourFunction = fn(first) { + let second = 20; + + first + second + third; +}; + +ourFunction(20) + first + second;` + + testIntegerObject(t, testEval(input), 70) +} + +func TestClosures(t *testing.T) { + input := ` +let newAdder = fn(x) { + fn(y) { x + y }; +}; + +let addTwo = newAdder(2); +addTwo(2);` + + testIntegerObject(t, testEval(input), 4) +} + +func TestStringLiteral(t *testing.T) { + input := `"Hello World!"` + + evaluated := testEval(input) + str, ok := evaluated.(*object.String) + if !ok { + t.Fatalf("object is not String. got=%T (%+v)", evaluated, evaluated) + } + + if str.Value != "Hello World!" { + t.Errorf("String has wrong value. got=%q", str.Value) + } +} + +func TestStringConcatenation(t *testing.T) { + input := `"Hello" + " " + "World!"` + + evaluated := testEval(input) + str, ok := evaluated.(*object.String) + if !ok { + t.Fatalf("object is not String. got=%T (%+v)", evaluated, evaluated) + } + + if str.Value != "Hello World!" { + t.Errorf("String has wrong value. got=%q", str.Value) + } +} + +func TestBuiltinFunctions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + {`len("")`, 0}, + {`len("four")`, 4}, + {`len("hello world")`, 11}, + {`len(1)`, "argument to `len` not supported, got INTEGER"}, + {`len("one", "two")`, "wrong number of arguments. got=2, want=1"}, + {`len([1, 2, 3])`, 3}, + {`len([])`, 0}, + {`puts("hello", "world!")`, nil}, + {`first([1, 2, 3])`, 1}, + {`first([])`, nil}, + {`first(1)`, "argument to `first` must be ARRAY, got INTEGER"}, + {`last([1, 2, 3])`, 3}, + {`last([])`, nil}, + {`last(1)`, "argument to `last` must be ARRAY, got INTEGER"}, + {`rest([1, 2, 3])`, []int{2, 3}}, + {`rest([])`, nil}, + {`push([], 1)`, []int{1}}, + {`push(1, 1)`, "argument to `push` must be ARRAY, got INTEGER"}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + + switch expected := tt.expected.(type) { + case int: + testIntegerObject(t, evaluated, int64(expected)) + case nil: + testNullObject(t, evaluated) + case string: + errObj, ok := evaluated.(*object.Error) + if !ok { + t.Errorf("object is not Error. got=%T (%+v)", + evaluated, evaluated) + continue + } + if errObj.Message != expected { + t.Errorf("wrong error message. expected=%q, got=%q", + expected, errObj.Message) + } + case []int: + array, ok := evaluated.(*object.Array) + if !ok { + t.Errorf("obj not Array. got=%T (%+v)", evaluated, evaluated) + continue + } + + if len(array.Elements) != len(expected) { + t.Errorf("wrong num of elements. want=%d, got=%d", + len(expected), len(array.Elements)) + continue + } + + for i, expectedElem := range expected { + testIntegerObject(t, array.Elements[i], int64(expectedElem)) + } + } + } +} + +func TestArrayLiterals(t *testing.T) { + input := "[1, 2 * 2, 3 + 3]" + + evaluated := testEval(input) + result, ok := evaluated.(*object.Array) + if !ok { + t.Fatalf("object is not Array. got=%T (%+v)", evaluated, evaluated) + } + + if len(result.Elements) != 3 { + t.Fatalf("array has wrong num of elements. got=%d", + len(result.Elements)) + } + + testIntegerObject(t, result.Elements[0], 1) + testIntegerObject(t, result.Elements[1], 4) + testIntegerObject(t, result.Elements[2], 6) +} + +func TestArrayIndexExpressions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + { + "[1, 2, 3][0]", + 1, + }, + { + "[1, 2, 3][1]", + 2, + }, + { + "[1, 2, 3][2]", + 3, + }, + { + "let i = 0; [1][i];", + 1, + }, + { + "[1, 2, 3][1 + 1];", + 3, + }, + { + "let myArray = [1, 2, 3]; myArray[2];", + 3, + }, + { + "let myArray = [1, 2, 3]; myArray[0] + myArray[1] + myArray[2];", + 6, + }, + { + "let myArray = [1, 2, 3]; let i = myArray[0]; myArray[i]", + 2, + }, + { + "[1, 2, 3][3]", + nil, + }, + { + "[1, 2, 3][-1]", + nil, + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + integer, ok := tt.expected.(int) + if ok { + testIntegerObject(t, evaluated, int64(integer)) + } else { + testNullObject(t, evaluated) + } + } +} + +func TestHashLiterals(t *testing.T) { + input := `let two = "two"; + { + "one": 10 - 9, + two: 1 + 1, + "thr" + "ee": 6 / 2, + 4: 4, + true: 5, + false: 6 + }` + + evaluated := testEval(input) + result, ok := evaluated.(*object.Hash) + if !ok { + t.Fatalf("Eval didn't return Hash. got=%T (%+v)", evaluated, evaluated) + } + + expected := map[object.HashKey]int64{ + (&object.String{Value: "one"}).HashKey(): 1, + (&object.String{Value: "two"}).HashKey(): 2, + (&object.String{Value: "three"}).HashKey(): 3, + (&object.Integer{Value: 4}).HashKey(): 4, + TRUE.HashKey(): 5, + FALSE.HashKey(): 6, + } + + if len(result.Pairs) != len(expected) { + t.Fatalf("Hash has wrong num of pairs. got=%d", len(result.Pairs)) + } + + for expectedKey, expectedValue := range expected { + pair, ok := result.Pairs[expectedKey] + if !ok { + t.Errorf("no pair for given key in Pairs") + } + + testIntegerObject(t, pair.Value, expectedValue) + } +} + +func TestHashIndexExpressions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + { + `{"foo": 5}["foo"]`, + 5, + }, + { + `{"foo": 5}["bar"]`, + nil, + }, + { + `let key = "foo"; {"foo": 5}[key]`, + 5, + }, + { + `{}["foo"]`, + nil, + }, + { + `{5: 5}[5]`, + 5, + }, + { + `{true: 5}[true]`, + 5, + }, + { + `{false: 5}[false]`, + 5, + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + integer, ok := tt.expected.(int) + if ok { + testIntegerObject(t, evaluated, int64(integer)) + } else { + testNullObject(t, evaluated) + } + } +} +func testEval(input string) object.Object { + l := lexer.New(input) + p := parser.New(l) + program := p.ParseProgram() + env := object.NewEnvironment() + + return Eval(program, env) +} + +func testIntegerObject(t *testing.T, obj object.Object, expected int64) bool { + result, ok := obj.(*object.Integer) + if !ok { + t.Errorf("object is not Integer. got=%T (%+v)", obj, obj) + return false + } + if result.Value != expected { + t.Errorf("object has wrong value. got=%d, want=%d", + result.Value, expected) + return false + } + + return true +} + +func testBooleanObject(t *testing.T, obj object.Object, expected bool) bool { + result, ok := obj.(*object.Boolean) + if !ok { + t.Errorf("object is not Boolean. got=%T (%+v)", obj, obj) + return false + } + if result.Value != expected { + t.Errorf("object has wrong value. got=%t, want=%t", + result.Value, expected) + return false + } + return true +} + +func testNullObject(t *testing.T, obj object.Object) bool { + if obj != NULL { + t.Errorf("object is not NULL. got=%T (%+v)", obj, obj) + return false + } + return true +} diff --git a/wcig_code_1_2/09/src/monkey/go.mod b/wcig_code_1_2/09/src/monkey/go.mod new file mode 100644 index 0000000..3064854 --- /dev/null +++ b/wcig_code_1_2/09/src/monkey/go.mod @@ -0,0 +1,3 @@ +module monkey + +go 1.14 diff --git a/wcig_code_1_2/09/src/monkey/lexer/lexer.go b/wcig_code_1_2/09/src/monkey/lexer/lexer.go new file mode 100644 index 0000000..db4f5f7 --- /dev/null +++ b/wcig_code_1_2/09/src/monkey/lexer/lexer.go @@ -0,0 +1,157 @@ +package lexer + +import "monkey/token" + +type Lexer struct { + input string + position int // current position in input (points to current char) + readPosition int // current reading position in input (after current char) + ch byte // current char under examination +} + +func New(input string) *Lexer { + l := &Lexer{input: input} + l.readChar() + return l +} + +func (l *Lexer) NextToken() token.Token { + var tok token.Token + + l.skipWhitespace() + + switch l.ch { + case '=': + if l.peekChar() == '=' { + ch := l.ch + l.readChar() + literal := string(ch) + string(l.ch) + tok = token.Token{Type: token.EQ, Literal: literal} + } else { + tok = newToken(token.ASSIGN, l.ch) + } + case '+': + tok = newToken(token.PLUS, l.ch) + case '-': + tok = newToken(token.MINUS, l.ch) + case '!': + if l.peekChar() == '=' { + ch := l.ch + l.readChar() + literal := string(ch) + string(l.ch) + tok = token.Token{Type: token.NOT_EQ, Literal: literal} + } else { + tok = newToken(token.BANG, l.ch) + } + case '/': + tok = newToken(token.SLASH, l.ch) + case '*': + tok = newToken(token.ASTERISK, l.ch) + case '<': + tok = newToken(token.LT, l.ch) + case '>': + tok = newToken(token.GT, l.ch) + case ';': + tok = newToken(token.SEMICOLON, l.ch) + case ':': + tok = newToken(token.COLON, l.ch) + case ',': + tok = newToken(token.COMMA, l.ch) + case '{': + tok = newToken(token.LBRACE, l.ch) + case '}': + tok = newToken(token.RBRACE, l.ch) + case '(': + tok = newToken(token.LPAREN, l.ch) + case ')': + tok = newToken(token.RPAREN, l.ch) + case '"': + tok.Type = token.STRING + tok.Literal = l.readString() + case '[': + tok = newToken(token.LBRACKET, l.ch) + case ']': + tok = newToken(token.RBRACKET, l.ch) + case 0: + tok.Literal = "" + tok.Type = token.EOF + default: + if isLetter(l.ch) { + tok.Literal = l.readIdentifier() + tok.Type = token.LookupIdent(tok.Literal) + return tok + } else if isDigit(l.ch) { + tok.Type = token.INT + tok.Literal = l.readNumber() + return tok + } else { + tok = newToken(token.ILLEGAL, l.ch) + } + } + + l.readChar() + return tok +} + +func (l *Lexer) skipWhitespace() { + for l.ch == ' ' || l.ch == '\t' || l.ch == '\n' || l.ch == '\r' { + l.readChar() + } +} + +func (l *Lexer) readChar() { + if l.readPosition >= len(l.input) { + l.ch = 0 + } else { + l.ch = l.input[l.readPosition] + } + l.position = l.readPosition + l.readPosition += 1 +} + +func (l *Lexer) peekChar() byte { + if l.readPosition >= len(l.input) { + return 0 + } else { + return l.input[l.readPosition] + } +} + +func (l *Lexer) readIdentifier() string { + position := l.position + for isLetter(l.ch) { + l.readChar() + } + return l.input[position:l.position] +} + +func (l *Lexer) readNumber() string { + position := l.position + for isDigit(l.ch) { + l.readChar() + } + return l.input[position:l.position] +} + +func (l *Lexer) readString() string { + position := l.position + 1 + for { + l.readChar() + if l.ch == '"' || l.ch == 0 { + break + } + } + return l.input[position:l.position] +} + +func isLetter(ch byte) bool { + return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' +} + +func isDigit(ch byte) bool { + return '0' <= ch && ch <= '9' +} + +func newToken(tokenType token.TokenType, ch byte) token.Token { + return token.Token{Type: tokenType, Literal: string(ch)} +} diff --git a/wcig_code_1_2/09/src/monkey/lexer/lexer_test.go b/wcig_code_1_2/09/src/monkey/lexer/lexer_test.go new file mode 100644 index 0000000..e4e64a4 --- /dev/null +++ b/wcig_code_1_2/09/src/monkey/lexer/lexer_test.go @@ -0,0 +1,143 @@ +package lexer + +import ( + "testing" + + "monkey/token" +) + +func TestNextToken(t *testing.T) { + input := `let five = 5; +let ten = 10; + +let add = fn(x, y) { + x + y; +}; + +let result = add(five, ten); +!-/*5; +5 < 10 > 5; + +if (5 < 10) { + return true; +} else { + return false; +} + +10 == 10; +10 != 9; +"foobar" +"foo bar" +[1, 2]; +{"foo": "bar"} +` + + tests := []struct { + expectedType token.TokenType + expectedLiteral string + }{ + {token.LET, "let"}, + {token.IDENT, "five"}, + {token.ASSIGN, "="}, + {token.INT, "5"}, + {token.SEMICOLON, ";"}, + {token.LET, "let"}, + {token.IDENT, "ten"}, + {token.ASSIGN, "="}, + {token.INT, "10"}, + {token.SEMICOLON, ";"}, + {token.LET, "let"}, + {token.IDENT, "add"}, + {token.ASSIGN, "="}, + {token.FUNCTION, "fn"}, + {token.LPAREN, "("}, + {token.IDENT, "x"}, + {token.COMMA, ","}, + {token.IDENT, "y"}, + {token.RPAREN, ")"}, + {token.LBRACE, "{"}, + {token.IDENT, "x"}, + {token.PLUS, "+"}, + {token.IDENT, "y"}, + {token.SEMICOLON, ";"}, + {token.RBRACE, "}"}, + {token.SEMICOLON, ";"}, + {token.LET, "let"}, + {token.IDENT, "result"}, + {token.ASSIGN, "="}, + {token.IDENT, "add"}, + {token.LPAREN, "("}, + {token.IDENT, "five"}, + {token.COMMA, ","}, + {token.IDENT, "ten"}, + {token.RPAREN, ")"}, + {token.SEMICOLON, ";"}, + {token.BANG, "!"}, + {token.MINUS, "-"}, + {token.SLASH, "/"}, + {token.ASTERISK, "*"}, + {token.INT, "5"}, + {token.SEMICOLON, ";"}, + {token.INT, "5"}, + {token.LT, "<"}, + {token.INT, "10"}, + {token.GT, ">"}, + {token.INT, "5"}, + {token.SEMICOLON, ";"}, + {token.IF, "if"}, + {token.LPAREN, "("}, + {token.INT, "5"}, + {token.LT, "<"}, + {token.INT, "10"}, + {token.RPAREN, ")"}, + {token.LBRACE, "{"}, + {token.RETURN, "return"}, + {token.TRUE, "true"}, + {token.SEMICOLON, ";"}, + {token.RBRACE, "}"}, + {token.ELSE, "else"}, + {token.LBRACE, "{"}, + {token.RETURN, "return"}, + {token.FALSE, "false"}, + {token.SEMICOLON, ";"}, + {token.RBRACE, "}"}, + {token.INT, "10"}, + {token.EQ, "=="}, + {token.INT, "10"}, + {token.SEMICOLON, ";"}, + {token.INT, "10"}, + {token.NOT_EQ, "!="}, + {token.INT, "9"}, + {token.SEMICOLON, ";"}, + {token.STRING, "foobar"}, + {token.STRING, "foo bar"}, + {token.LBRACKET, "["}, + {token.INT, "1"}, + {token.COMMA, ","}, + {token.INT, "2"}, + {token.RBRACKET, "]"}, + {token.SEMICOLON, ";"}, + {token.LBRACE, "{"}, + {token.STRING, "foo"}, + {token.COLON, ":"}, + {token.STRING, "bar"}, + {token.RBRACE, "}"}, + {token.EOF, ""}, + } + + l := New(input) + + for i, tt := range tests { + tok := l.NextToken() + + if tok.Type != tt.expectedType { + t.Fatalf("tests[%d] - tokentype wrong. expected=%q, got=%q", + i, tt.expectedType, tok.Type) + } + + if tok.Literal != tt.expectedLiteral { + t.Fatalf("tests[%d] - literal wrong. expected=%q, got=%q", + i, tt.expectedLiteral, tok.Literal) + } + } +} diff --git a/wcig_code_1_2/09/src/monkey/main.go b/wcig_code_1_2/09/src/monkey/main.go new file mode 100644 index 0000000..1d27138 --- /dev/null +++ b/wcig_code_1_2/09/src/monkey/main.go @@ -0,0 +1,19 @@ +package main + +import ( + "fmt" + "monkey/repl" + "os" + "os/user" +) + +func main() { + user, err := user.Current() + if err != nil { + panic(err) + } + fmt.Printf("Hello %s! This is the Monkey programming language!\n", + user.Username) + fmt.Printf("Feel free to type in commands\n") + repl.Start(os.Stdin, os.Stdout) +} diff --git a/wcig_code_1_2/09/src/monkey/object/builtins.go b/wcig_code_1_2/09/src/monkey/object/builtins.go new file mode 100644 index 0000000..28dca2d --- /dev/null +++ b/wcig_code_1_2/09/src/monkey/object/builtins.go @@ -0,0 +1,143 @@ +package object + +import "fmt" + +var Builtins = []struct { + Name string + Builtin *Builtin +}{ + { + "len", + &Builtin{Fn: func(args ...Object) Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", + len(args)) + } + + switch arg := args[0].(type) { + case *Array: + return &Integer{Value: int64(len(arg.Elements))} + case *String: + return &Integer{Value: int64(len(arg.Value))} + default: + return newError("argument to `len` not supported, got %s", + args[0].Type()) + } + }, + }, + }, + { + "puts", + &Builtin{Fn: func(args ...Object) Object { + for _, arg := range args { + fmt.Println(arg.Inspect()) + } + + return nil + }, + }, + }, + { + "first", + &Builtin{Fn: func(args ...Object) Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", + len(args)) + } + if args[0].Type() != ARRAY_OBJ { + return newError("argument to `first` must be ARRAY, got %s", + args[0].Type()) + } + + arr := args[0].(*Array) + if len(arr.Elements) > 0 { + return arr.Elements[0] + } + + return nil + }, + }, + }, + { + "last", + &Builtin{Fn: func(args ...Object) Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", + len(args)) + } + if args[0].Type() != ARRAY_OBJ { + return newError("argument to `last` must be ARRAY, got %s", + args[0].Type()) + } + + arr := args[0].(*Array) + length := len(arr.Elements) + if length > 0 { + return arr.Elements[length-1] + } + + return nil + }, + }, + }, + { + "rest", + &Builtin{Fn: func(args ...Object) Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", + len(args)) + } + if args[0].Type() != ARRAY_OBJ { + return newError("argument to `rest` must be ARRAY, got %s", + args[0].Type()) + } + + arr := args[0].(*Array) + length := len(arr.Elements) + if length > 0 { + newElements := make([]Object, length-1, length-1) + copy(newElements, arr.Elements[1:length]) + return &Array{Elements: newElements} + } + + return nil + }, + }, + }, + { + "push", + &Builtin{Fn: func(args ...Object) Object { + if len(args) != 2 { + return newError("wrong number of arguments. got=%d, want=2", + len(args)) + } + if args[0].Type() != ARRAY_OBJ { + return newError("argument to `push` must be ARRAY, got %s", + args[0].Type()) + } + + arr := args[0].(*Array) + length := len(arr.Elements) + + newElements := make([]Object, length+1, length+1) + copy(newElements, arr.Elements) + newElements[length] = args[1] + + return &Array{Elements: newElements} + }, + }, + }, +} + +func newError(format string, a ...interface{}) *Error { + return &Error{Message: fmt.Sprintf(format, a...)} +} + +func GetBuiltinByName(name string) *Builtin { + for _, def := range Builtins { + if def.Name == name { + return def.Builtin + } + } + return nil +} diff --git a/wcig_code_1_2/09/src/monkey/object/environment.go b/wcig_code_1_2/09/src/monkey/object/environment.go new file mode 100644 index 0000000..6f31070 --- /dev/null +++ b/wcig_code_1_2/09/src/monkey/object/environment.go @@ -0,0 +1,30 @@ +package object + +func NewEnclosedEnvironment(outer *Environment) *Environment { + env := NewEnvironment() + env.outer = outer + return env +} + +func NewEnvironment() *Environment { + s := make(map[string]Object) + return &Environment{store: s, outer: nil} +} + +type Environment struct { + store map[string]Object + outer *Environment +} + +func (e *Environment) Get(name string) (Object, bool) { + obj, ok := e.store[name] + if !ok && e.outer != nil { + obj, ok = e.outer.Get(name) + } + return obj, ok +} + +func (e *Environment) Set(name string, val Object) Object { + e.store[name] = val + return val +} diff --git a/wcig_code_1_2/09/src/monkey/object/object.go b/wcig_code_1_2/09/src/monkey/object/object.go new file mode 100644 index 0000000..b5c22e4 --- /dev/null +++ b/wcig_code_1_2/09/src/monkey/object/object.go @@ -0,0 +1,208 @@ +package object + +import ( + "bytes" + "fmt" + "hash/fnv" + "monkey/ast" + "monkey/code" + "strings" +) + +type BuiltinFunction func(args ...Object) Object + +type ObjectType string + +const ( + NULL_OBJ = "NULL" + ERROR_OBJ = "ERROR" + + INTEGER_OBJ = "INTEGER" + BOOLEAN_OBJ = "BOOLEAN" + STRING_OBJ = "STRING" + + RETURN_VALUE_OBJ = "RETURN_VALUE" + + FUNCTION_OBJ = "FUNCTION" + BUILTIN_OBJ = "BUILTIN" + + ARRAY_OBJ = "ARRAY" + HASH_OBJ = "HASH" + + COMPILED_FUNCTION_OBJ = "COMPILED_FUNCTION_OBJ" + + CLOSURE_OBJ = "CLOSURE" +) + +type HashKey struct { + Type ObjectType + Value uint64 +} + +type Hashable interface { + HashKey() HashKey +} + +type Object interface { + Type() ObjectType + Inspect() string +} + +type Integer struct { + Value int64 +} + +func (i *Integer) Type() ObjectType { return INTEGER_OBJ } +func (i *Integer) Inspect() string { return fmt.Sprintf("%d", i.Value) } +func (i *Integer) HashKey() HashKey { + return HashKey{Type: i.Type(), Value: uint64(i.Value)} +} + +type Boolean struct { + Value bool +} + +func (b *Boolean) Type() ObjectType { return BOOLEAN_OBJ } +func (b *Boolean) Inspect() string { return fmt.Sprintf("%t", b.Value) } +func (b *Boolean) HashKey() HashKey { + var value uint64 + + if b.Value { + value = 1 + } else { + value = 0 + } + + return HashKey{Type: b.Type(), Value: value} +} + +type Null struct{} + +func (n *Null) Type() ObjectType { return NULL_OBJ } +func (n *Null) Inspect() string { return "null" } + +type ReturnValue struct { + Value Object +} + +func (rv *ReturnValue) Type() ObjectType { return RETURN_VALUE_OBJ } +func (rv *ReturnValue) Inspect() string { return rv.Value.Inspect() } + +type Error struct { + Message string +} + +func (e *Error) Type() ObjectType { return ERROR_OBJ } +func (e *Error) Inspect() string { return "ERROR: " + e.Message } + +type Function struct { + Parameters []*ast.Identifier + Body *ast.BlockStatement + Env *Environment +} + +func (f *Function) Type() ObjectType { return FUNCTION_OBJ } +func (f *Function) Inspect() string { + var out bytes.Buffer + + params := []string{} + for _, p := range f.Parameters { + params = append(params, p.String()) + } + + out.WriteString("fn") + out.WriteString("(") + out.WriteString(strings.Join(params, ", ")) + out.WriteString(") {\n") + out.WriteString(f.Body.String()) + out.WriteString("\n}") + + return out.String() +} + +type String struct { + Value string +} + +func (s *String) Type() ObjectType { return STRING_OBJ } +func (s *String) Inspect() string { return s.Value } +func (s *String) HashKey() HashKey { + h := fnv.New64a() + h.Write([]byte(s.Value)) + + return HashKey{Type: s.Type(), Value: h.Sum64()} +} + +type Builtin struct { + Fn BuiltinFunction +} + +func (b *Builtin) Type() ObjectType { return BUILTIN_OBJ } +func (b *Builtin) Inspect() string { return "builtin function" } + +type Array struct { + Elements []Object +} + +func (ao *Array) Type() ObjectType { return ARRAY_OBJ } +func (ao *Array) Inspect() string { + var out bytes.Buffer + + elements := []string{} + for _, e := range ao.Elements { + elements = append(elements, e.Inspect()) + } + + out.WriteString("[") + out.WriteString(strings.Join(elements, ", ")) + out.WriteString("]") + + return out.String() +} + +type HashPair struct { + Key Object + Value Object +} + +type Hash struct { + Pairs map[HashKey]HashPair +} + +func (h *Hash) Type() ObjectType { return HASH_OBJ } +func (h *Hash) Inspect() string { + var out bytes.Buffer + + pairs := []string{} + for _, pair := range h.Pairs { + pairs = append(pairs, fmt.Sprintf("%s: %s", + pair.Key.Inspect(), pair.Value.Inspect())) + } + + out.WriteString("{") + out.WriteString(strings.Join(pairs, ", ")) + out.WriteString("}") + + return out.String() +} + +type CompiledFunction struct { + Instructions code.Instructions + NumLocals int + NumParameters int +} + +func (cf *CompiledFunction) Type() ObjectType { return COMPILED_FUNCTION_OBJ } +func (cf *CompiledFunction) Inspect() string { + return fmt.Sprintf("CompiledFunction[%p]", cf) +} + +type Closure struct { + Fn *CompiledFunction + Free []Object +} + +func (c *Closure) Type() ObjectType { return CLOSURE_OBJ } +func (c *Closure) Inspect() string { + return fmt.Sprintf("Closure[%p]", c) +} diff --git a/wcig_code_1_2/09/src/monkey/object/object_test.go b/wcig_code_1_2/09/src/monkey/object/object_test.go new file mode 100644 index 0000000..63228a2 --- /dev/null +++ b/wcig_code_1_2/09/src/monkey/object/object_test.go @@ -0,0 +1,60 @@ +package object + +import "testing" + +func TestStringHashKey(t *testing.T) { + hello1 := &String{Value: "Hello World"} + hello2 := &String{Value: "Hello World"} + diff1 := &String{Value: "My name is johnny"} + diff2 := &String{Value: "My name is johnny"} + + if hello1.HashKey() != hello2.HashKey() { + t.Errorf("strings with same content have different hash keys") + } + + if diff1.HashKey() != diff2.HashKey() { + t.Errorf("strings with same content have different hash keys") + } + + if hello1.HashKey() == diff1.HashKey() { + t.Errorf("strings with different content have same hash keys") + } +} + +func TestBooleanHashKey(t *testing.T) { + true1 := &Boolean{Value: true} + true2 := &Boolean{Value: true} + false1 := &Boolean{Value: false} + false2 := &Boolean{Value: false} + + if true1.HashKey() != true2.HashKey() { + t.Errorf("trues do not have same hash key") + } + + if false1.HashKey() != false2.HashKey() { + t.Errorf("falses do not have same hash key") + } + + if true1.HashKey() == false1.HashKey() { + t.Errorf("true has same hash key as false") + } +} + +func TestIntegerHashKey(t *testing.T) { + one1 := &Integer{Value: 1} + one2 := &Integer{Value: 1} + two1 := &Integer{Value: 2} + two2 := &Integer{Value: 2} + + if one1.HashKey() != one2.HashKey() { + t.Errorf("integers with same content have twoerent hash keys") + } + + if two1.HashKey() != two2.HashKey() { + t.Errorf("integers with same content have twoerent hash keys") + } + + if one1.HashKey() == two1.HashKey() { + t.Errorf("integers with twoerent content have same hash keys") + } +} diff --git a/wcig_code_1_2/09/src/monkey/parser/parser.go b/wcig_code_1_2/09/src/monkey/parser/parser.go new file mode 100644 index 0000000..8a6e21d --- /dev/null +++ b/wcig_code_1_2/09/src/monkey/parser/parser.go @@ -0,0 +1,495 @@ +package parser + +import ( + "fmt" + "monkey/ast" + "monkey/lexer" + "monkey/token" + "strconv" +) + +const ( + _ int = iota + LOWEST + EQUALS // == + LESSGREATER // > or < + SUM // + + PRODUCT // * + PREFIX // -X or !X + CALL // myFunction(X) + INDEX // array[index] +) + +var precedences = map[token.TokenType]int{ + token.EQ: EQUALS, + token.NOT_EQ: EQUALS, + token.LT: LESSGREATER, + token.GT: LESSGREATER, + token.PLUS: SUM, + token.MINUS: SUM, + token.SLASH: PRODUCT, + token.ASTERISK: PRODUCT, + token.LPAREN: CALL, + token.LBRACKET: INDEX, +} + +type ( + prefixParseFn func() ast.Expression + infixParseFn func(ast.Expression) ast.Expression +) + +type Parser struct { + l *lexer.Lexer + errors []string + + curToken token.Token + peekToken token.Token + + prefixParseFns map[token.TokenType]prefixParseFn + infixParseFns map[token.TokenType]infixParseFn +} + +func New(l *lexer.Lexer) *Parser { + p := &Parser{ + l: l, + errors: []string{}, + } + + p.prefixParseFns = make(map[token.TokenType]prefixParseFn) + p.registerPrefix(token.IDENT, p.parseIdentifier) + p.registerPrefix(token.INT, p.parseIntegerLiteral) + p.registerPrefix(token.STRING, p.parseStringLiteral) + p.registerPrefix(token.BANG, p.parsePrefixExpression) + p.registerPrefix(token.MINUS, p.parsePrefixExpression) + p.registerPrefix(token.TRUE, p.parseBoolean) + p.registerPrefix(token.FALSE, p.parseBoolean) + p.registerPrefix(token.LPAREN, p.parseGroupedExpression) + p.registerPrefix(token.IF, p.parseIfExpression) + p.registerPrefix(token.FUNCTION, p.parseFunctionLiteral) + p.registerPrefix(token.LBRACKET, p.parseArrayLiteral) + p.registerPrefix(token.LBRACE, p.parseHashLiteral) + + p.infixParseFns = make(map[token.TokenType]infixParseFn) + p.registerInfix(token.PLUS, p.parseInfixExpression) + p.registerInfix(token.MINUS, p.parseInfixExpression) + p.registerInfix(token.SLASH, p.parseInfixExpression) + p.registerInfix(token.ASTERISK, p.parseInfixExpression) + p.registerInfix(token.EQ, p.parseInfixExpression) + p.registerInfix(token.NOT_EQ, p.parseInfixExpression) + p.registerInfix(token.LT, p.parseInfixExpression) + p.registerInfix(token.GT, p.parseInfixExpression) + + p.registerInfix(token.LPAREN, p.parseCallExpression) + p.registerInfix(token.LBRACKET, p.parseIndexExpression) + + // Read two tokens, so curToken and peekToken are both set + p.nextToken() + p.nextToken() + + return p +} + +func (p *Parser) nextToken() { + p.curToken = p.peekToken + p.peekToken = p.l.NextToken() +} + +func (p *Parser) curTokenIs(t token.TokenType) bool { + return p.curToken.Type == t +} + +func (p *Parser) peekTokenIs(t token.TokenType) bool { + return p.peekToken.Type == t +} + +func (p *Parser) expectPeek(t token.TokenType) bool { + if p.peekTokenIs(t) { + p.nextToken() + return true + } else { + p.peekError(t) + return false + } +} + +func (p *Parser) Errors() []string { + return p.errors +} + +func (p *Parser) peekError(t token.TokenType) { + msg := fmt.Sprintf("expected next token to be %s, got %s instead", + t, p.peekToken.Type) + p.errors = append(p.errors, msg) +} + +func (p *Parser) noPrefixParseFnError(t token.TokenType) { + msg := fmt.Sprintf("no prefix parse function for %s found", t) + p.errors = append(p.errors, msg) +} + +func (p *Parser) ParseProgram() *ast.Program { + program := &ast.Program{} + program.Statements = []ast.Statement{} + + for !p.curTokenIs(token.EOF) { + stmt := p.parseStatement() + if stmt != nil { + program.Statements = append(program.Statements, stmt) + } + p.nextToken() + } + + return program +} + +func (p *Parser) parseStatement() ast.Statement { + switch p.curToken.Type { + case token.LET: + return p.parseLetStatement() + case token.RETURN: + return p.parseReturnStatement() + default: + return p.parseExpressionStatement() + } +} + +func (p *Parser) parseLetStatement() *ast.LetStatement { + stmt := &ast.LetStatement{Token: p.curToken} + + if !p.expectPeek(token.IDENT) { + return nil + } + + stmt.Name = &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + + if !p.expectPeek(token.ASSIGN) { + return nil + } + + p.nextToken() + + stmt.Value = p.parseExpression(LOWEST) + + if fl, ok := stmt.Value.(*ast.FunctionLiteral); ok { + fl.Name = stmt.Name.Value + } + + if p.peekTokenIs(token.SEMICOLON) { + p.nextToken() + } + + return stmt +} + +func (p *Parser) parseReturnStatement() *ast.ReturnStatement { + stmt := &ast.ReturnStatement{Token: p.curToken} + + p.nextToken() + + stmt.ReturnValue = p.parseExpression(LOWEST) + + if p.peekTokenIs(token.SEMICOLON) { + p.nextToken() + } + + return stmt +} + +func (p *Parser) parseExpressionStatement() *ast.ExpressionStatement { + stmt := &ast.ExpressionStatement{Token: p.curToken} + + stmt.Expression = p.parseExpression(LOWEST) + + if p.peekTokenIs(token.SEMICOLON) { + p.nextToken() + } + + return stmt +} + +func (p *Parser) parseExpression(precedence int) ast.Expression { + prefix := p.prefixParseFns[p.curToken.Type] + if prefix == nil { + p.noPrefixParseFnError(p.curToken.Type) + return nil + } + leftExp := prefix() + + for !p.peekTokenIs(token.SEMICOLON) && precedence < p.peekPrecedence() { + infix := p.infixParseFns[p.peekToken.Type] + if infix == nil { + return leftExp + } + + p.nextToken() + + leftExp = infix(leftExp) + } + + return leftExp +} + +func (p *Parser) peekPrecedence() int { + if p, ok := precedences[p.peekToken.Type]; ok { + return p + } + + return LOWEST +} + +func (p *Parser) curPrecedence() int { + if p, ok := precedences[p.curToken.Type]; ok { + return p + } + + return LOWEST +} + +func (p *Parser) parseIdentifier() ast.Expression { + return &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} +} + +func (p *Parser) parseIntegerLiteral() ast.Expression { + lit := &ast.IntegerLiteral{Token: p.curToken} + + value, err := strconv.ParseInt(p.curToken.Literal, 0, 64) + if err != nil { + msg := fmt.Sprintf("could not parse %q as integer", p.curToken.Literal) + p.errors = append(p.errors, msg) + return nil + } + + lit.Value = value + + return lit +} + +func (p *Parser) parseStringLiteral() ast.Expression { + return &ast.StringLiteral{Token: p.curToken, Value: p.curToken.Literal} +} + +func (p *Parser) parsePrefixExpression() ast.Expression { + expression := &ast.PrefixExpression{ + Token: p.curToken, + Operator: p.curToken.Literal, + } + + p.nextToken() + + expression.Right = p.parseExpression(PREFIX) + + return expression +} + +func (p *Parser) parseInfixExpression(left ast.Expression) ast.Expression { + expression := &ast.InfixExpression{ + Token: p.curToken, + Operator: p.curToken.Literal, + Left: left, + } + + precedence := p.curPrecedence() + p.nextToken() + expression.Right = p.parseExpression(precedence) + + return expression +} + +func (p *Parser) parseBoolean() ast.Expression { + return &ast.Boolean{Token: p.curToken, Value: p.curTokenIs(token.TRUE)} +} + +func (p *Parser) parseGroupedExpression() ast.Expression { + p.nextToken() + + exp := p.parseExpression(LOWEST) + + if !p.expectPeek(token.RPAREN) { + return nil + } + + return exp +} + +func (p *Parser) parseIfExpression() ast.Expression { + expression := &ast.IfExpression{Token: p.curToken} + + if !p.expectPeek(token.LPAREN) { + return nil + } + + p.nextToken() + expression.Condition = p.parseExpression(LOWEST) + + if !p.expectPeek(token.RPAREN) { + return nil + } + + if !p.expectPeek(token.LBRACE) { + return nil + } + + expression.Consequence = p.parseBlockStatement() + + if p.peekTokenIs(token.ELSE) { + p.nextToken() + + if !p.expectPeek(token.LBRACE) { + return nil + } + + expression.Alternative = p.parseBlockStatement() + } + + return expression +} + +func (p *Parser) parseBlockStatement() *ast.BlockStatement { + block := &ast.BlockStatement{Token: p.curToken} + block.Statements = []ast.Statement{} + + p.nextToken() + + for !p.curTokenIs(token.RBRACE) && !p.curTokenIs(token.EOF) { + stmt := p.parseStatement() + if stmt != nil { + block.Statements = append(block.Statements, stmt) + } + p.nextToken() + } + + return block +} + +func (p *Parser) parseFunctionLiteral() ast.Expression { + lit := &ast.FunctionLiteral{Token: p.curToken} + + if !p.expectPeek(token.LPAREN) { + return nil + } + + lit.Parameters = p.parseFunctionParameters() + + if !p.expectPeek(token.LBRACE) { + return nil + } + + lit.Body = p.parseBlockStatement() + + return lit +} + +func (p *Parser) parseFunctionParameters() []*ast.Identifier { + identifiers := []*ast.Identifier{} + + if p.peekTokenIs(token.RPAREN) { + p.nextToken() + return identifiers + } + + p.nextToken() + + ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + identifiers = append(identifiers, ident) + + for p.peekTokenIs(token.COMMA) { + p.nextToken() + p.nextToken() + ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + identifiers = append(identifiers, ident) + } + + if !p.expectPeek(token.RPAREN) { + return nil + } + + return identifiers +} + +func (p *Parser) parseCallExpression(function ast.Expression) ast.Expression { + exp := &ast.CallExpression{Token: p.curToken, Function: function} + exp.Arguments = p.parseExpressionList(token.RPAREN) + return exp +} + +func (p *Parser) parseExpressionList(end token.TokenType) []ast.Expression { + list := []ast.Expression{} + + if p.peekTokenIs(end) { + p.nextToken() + return list + } + + p.nextToken() + list = append(list, p.parseExpression(LOWEST)) + + for p.peekTokenIs(token.COMMA) { + p.nextToken() + p.nextToken() + list = append(list, p.parseExpression(LOWEST)) + } + + if !p.expectPeek(end) { + return nil + } + + return list +} + +func (p *Parser) parseArrayLiteral() ast.Expression { + array := &ast.ArrayLiteral{Token: p.curToken} + + array.Elements = p.parseExpressionList(token.RBRACKET) + + return array +} + +func (p *Parser) parseIndexExpression(left ast.Expression) ast.Expression { + exp := &ast.IndexExpression{Token: p.curToken, Left: left} + + p.nextToken() + exp.Index = p.parseExpression(LOWEST) + + if !p.expectPeek(token.RBRACKET) { + return nil + } + + return exp +} + +func (p *Parser) parseHashLiteral() ast.Expression { + hash := &ast.HashLiteral{Token: p.curToken} + hash.Pairs = make(map[ast.Expression]ast.Expression) + + for !p.peekTokenIs(token.RBRACE) { + p.nextToken() + key := p.parseExpression(LOWEST) + + if !p.expectPeek(token.COLON) { + return nil + } + + p.nextToken() + value := p.parseExpression(LOWEST) + + hash.Pairs[key] = value + + if !p.peekTokenIs(token.RBRACE) && !p.expectPeek(token.COMMA) { + return nil + } + } + + if !p.expectPeek(token.RBRACE) { + return nil + } + + return hash +} + +func (p *Parser) registerPrefix(tokenType token.TokenType, fn prefixParseFn) { + p.prefixParseFns[tokenType] = fn +} + +func (p *Parser) registerInfix(tokenType token.TokenType, fn infixParseFn) { + p.infixParseFns[tokenType] = fn +} diff --git a/wcig_code_1_2/09/src/monkey/parser/parser_test.go b/wcig_code_1_2/09/src/monkey/parser/parser_test.go new file mode 100644 index 0000000..3430028 --- /dev/null +++ b/wcig_code_1_2/09/src/monkey/parser/parser_test.go @@ -0,0 +1,1114 @@ +package parser + +import ( + "fmt" + "monkey/ast" + "monkey/lexer" + "testing" +) + +func TestLetStatements(t *testing.T) { + tests := []struct { + input string + expectedIdentifier string + expectedValue interface{} + }{ + {"let x = 5;", "x", 5}, + {"let y = true;", "y", true}, + {"let foobar = y;", "foobar", "y"}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain 1 statements. got=%d", + len(program.Statements)) + } + + stmt := program.Statements[0] + if !testLetStatement(t, stmt, tt.expectedIdentifier) { + return + } + + val := stmt.(*ast.LetStatement).Value + if !testLiteralExpression(t, val, tt.expectedValue) { + return + } + } +} + +func TestReturnStatements(t *testing.T) { + tests := []struct { + input string + expectedValue interface{} + }{ + {"return 5;", 5}, + {"return true;", true}, + {"return foobar;", "foobar"}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain 1 statements. got=%d", + len(program.Statements)) + } + + stmt := program.Statements[0] + returnStmt, ok := stmt.(*ast.ReturnStatement) + if !ok { + t.Fatalf("stmt not *ast.returnStatement. got=%T", stmt) + } + if returnStmt.TokenLiteral() != "return" { + t.Fatalf("returnStmt.TokenLiteral not 'return', got %q", + returnStmt.TokenLiteral()) + } + if testLiteralExpression(t, returnStmt.ReturnValue, tt.expectedValue) { + return + } + } +} + +func TestIdentifierExpression(t *testing.T) { + input := "foobar;" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program has not enough statements. got=%d", + len(program.Statements)) + } + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + ident, ok := stmt.Expression.(*ast.Identifier) + if !ok { + t.Fatalf("exp not *ast.Identifier. got=%T", stmt.Expression) + } + if ident.Value != "foobar" { + t.Errorf("ident.Value not %s. got=%s", "foobar", ident.Value) + } + if ident.TokenLiteral() != "foobar" { + t.Errorf("ident.TokenLiteral not %s. got=%s", "foobar", + ident.TokenLiteral()) + } +} + +func TestIntegerLiteralExpression(t *testing.T) { + input := "5;" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program has not enough statements. got=%d", + len(program.Statements)) + } + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + literal, ok := stmt.Expression.(*ast.IntegerLiteral) + if !ok { + t.Fatalf("exp not *ast.IntegerLiteral. got=%T", stmt.Expression) + } + if literal.Value != 5 { + t.Errorf("literal.Value not %d. got=%d", 5, literal.Value) + } + if literal.TokenLiteral() != "5" { + t.Errorf("literal.TokenLiteral not %s. got=%s", "5", + literal.TokenLiteral()) + } +} + +func TestParsingPrefixExpressions(t *testing.T) { + prefixTests := []struct { + input string + operator string + value interface{} + }{ + {"!5;", "!", 5}, + {"-15;", "-", 15}, + {"!foobar;", "!", "foobar"}, + {"-foobar;", "-", "foobar"}, + {"!true;", "!", true}, + {"!false;", "!", false}, + } + + for _, tt := range prefixTests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.PrefixExpression) + if !ok { + t.Fatalf("stmt is not ast.PrefixExpression. got=%T", stmt.Expression) + } + if exp.Operator != tt.operator { + t.Fatalf("exp.Operator is not '%s'. got=%s", + tt.operator, exp.Operator) + } + if !testLiteralExpression(t, exp.Right, tt.value) { + return + } + } +} + +func TestParsingInfixExpressions(t *testing.T) { + infixTests := []struct { + input string + leftValue interface{} + operator string + rightValue interface{} + }{ + {"5 + 5;", 5, "+", 5}, + {"5 - 5;", 5, "-", 5}, + {"5 * 5;", 5, "*", 5}, + {"5 / 5;", 5, "/", 5}, + {"5 > 5;", 5, ">", 5}, + {"5 < 5;", 5, "<", 5}, + {"5 == 5;", 5, "==", 5}, + {"5 != 5;", 5, "!=", 5}, + {"foobar + barfoo;", "foobar", "+", "barfoo"}, + {"foobar - barfoo;", "foobar", "-", "barfoo"}, + {"foobar * barfoo;", "foobar", "*", "barfoo"}, + {"foobar / barfoo;", "foobar", "/", "barfoo"}, + {"foobar > barfoo;", "foobar", ">", "barfoo"}, + {"foobar < barfoo;", "foobar", "<", "barfoo"}, + {"foobar == barfoo;", "foobar", "==", "barfoo"}, + {"foobar != barfoo;", "foobar", "!=", "barfoo"}, + {"true == true", true, "==", true}, + {"true != false", true, "!=", false}, + {"false == false", false, "==", false}, + } + + for _, tt := range infixTests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + if !testInfixExpression(t, stmt.Expression, tt.leftValue, + tt.operator, tt.rightValue) { + return + } + } +} + +func TestOperatorPrecedenceParsing(t *testing.T) { + tests := []struct { + input string + expected string + }{ + { + "-a * b", + "((-a) * b)", + }, + { + "!-a", + "(!(-a))", + }, + { + "a + b + c", + "((a + b) + c)", + }, + { + "a + b - c", + "((a + b) - c)", + }, + { + "a * b * c", + "((a * b) * c)", + }, + { + "a * b / c", + "((a * b) / c)", + }, + { + "a + b / c", + "(a + (b / c))", + }, + { + "a + b * c + d / e - f", + "(((a + (b * c)) + (d / e)) - f)", + }, + { + "3 + 4; -5 * 5", + "(3 + 4)((-5) * 5)", + }, + { + "5 > 4 == 3 < 4", + "((5 > 4) == (3 < 4))", + }, + { + "5 < 4 != 3 > 4", + "((5 < 4) != (3 > 4))", + }, + { + "3 + 4 * 5 == 3 * 1 + 4 * 5", + "((3 + (4 * 5)) == ((3 * 1) + (4 * 5)))", + }, + { + "true", + "true", + }, + { + "false", + "false", + }, + { + "3 > 5 == false", + "((3 > 5) == false)", + }, + { + "3 < 5 == true", + "((3 < 5) == true)", + }, + { + "1 + (2 + 3) + 4", + "((1 + (2 + 3)) + 4)", + }, + { + "(5 + 5) * 2", + "((5 + 5) * 2)", + }, + { + "2 / (5 + 5)", + "(2 / (5 + 5))", + }, + { + "(5 + 5) * 2 * (5 + 5)", + "(((5 + 5) * 2) * (5 + 5))", + }, + { + "-(5 + 5)", + "(-(5 + 5))", + }, + { + "!(true == true)", + "(!(true == true))", + }, + { + "a + add(b * c) + d", + "((a + add((b * c))) + d)", + }, + { + "add(a, b, 1, 2 * 3, 4 + 5, add(6, 7 * 8))", + "add(a, b, 1, (2 * 3), (4 + 5), add(6, (7 * 8)))", + }, + { + "add(a + b + c * d / f + g)", + "add((((a + b) + ((c * d) / f)) + g))", + }, + { + "a * [1, 2, 3, 4][b * c] * d", + "((a * ([1, 2, 3, 4][(b * c)])) * d)", + }, + { + "add(a * b[2], b[1], 2 * [1, 2][1])", + "add((a * (b[2])), (b[1]), (2 * ([1, 2][1])))", + }, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + actual := program.String() + if actual != tt.expected { + t.Errorf("expected=%q, got=%q", tt.expected, actual) + } + } +} + +func TestBooleanExpression(t *testing.T) { + tests := []struct { + input string + expectedBoolean bool + }{ + {"true;", true}, + {"false;", false}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program has not enough statements. got=%d", + len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + boolean, ok := stmt.Expression.(*ast.Boolean) + if !ok { + t.Fatalf("exp not *ast.Boolean. got=%T", stmt.Expression) + } + if boolean.Value != tt.expectedBoolean { + t.Errorf("boolean.Value not %t. got=%t", tt.expectedBoolean, + boolean.Value) + } + } +} + +func TestIfExpression(t *testing.T) { + input := `if (x < y) { x }` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.IfExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.IfExpression. got=%T", + stmt.Expression) + } + + if !testInfixExpression(t, exp.Condition, "x", "<", "y") { + return + } + + if len(exp.Consequence.Statements) != 1 { + t.Errorf("consequence is not 1 statements. got=%d\n", + len(exp.Consequence.Statements)) + } + + consequence, ok := exp.Consequence.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", + exp.Consequence.Statements[0]) + } + + if !testIdentifier(t, consequence.Expression, "x") { + return + } + + if exp.Alternative != nil { + t.Errorf("exp.Alternative.Statements was not nil. got=%+v", exp.Alternative) + } +} + +func TestIfElseExpression(t *testing.T) { + input := `if (x < y) { x } else { y }` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.IfExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.IfExpression. got=%T", stmt.Expression) + } + + if !testInfixExpression(t, exp.Condition, "x", "<", "y") { + return + } + + if len(exp.Consequence.Statements) != 1 { + t.Errorf("consequence is not 1 statements. got=%d\n", + len(exp.Consequence.Statements)) + } + + consequence, ok := exp.Consequence.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", + exp.Consequence.Statements[0]) + } + + if !testIdentifier(t, consequence.Expression, "x") { + return + } + + if len(exp.Alternative.Statements) != 1 { + t.Errorf("exp.Alternative.Statements does not contain 1 statements. got=%d\n", + len(exp.Alternative.Statements)) + } + + alternative, ok := exp.Alternative.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", + exp.Alternative.Statements[0]) + } + + if !testIdentifier(t, alternative.Expression, "y") { + return + } +} + +func TestFunctionLiteralParsing(t *testing.T) { + input := `fn(x, y) { x + y; }` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + function, ok := stmt.Expression.(*ast.FunctionLiteral) + if !ok { + t.Fatalf("stmt.Expression is not ast.FunctionLiteral. got=%T", + stmt.Expression) + } + + if len(function.Parameters) != 2 { + t.Fatalf("function literal parameters wrong. want 2, got=%d\n", + len(function.Parameters)) + } + + testLiteralExpression(t, function.Parameters[0], "x") + testLiteralExpression(t, function.Parameters[1], "y") + + if len(function.Body.Statements) != 1 { + t.Fatalf("function.Body.Statements has not 1 statements. got=%d\n", + len(function.Body.Statements)) + } + + bodyStmt, ok := function.Body.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("function body stmt is not ast.ExpressionStatement. got=%T", + function.Body.Statements[0]) + } + + testInfixExpression(t, bodyStmt.Expression, "x", "+", "y") +} + +func TestFunctionParameterParsing(t *testing.T) { + tests := []struct { + input string + expectedParams []string + }{ + {input: "fn() {};", expectedParams: []string{}}, + {input: "fn(x) {};", expectedParams: []string{"x"}}, + {input: "fn(x, y, z) {};", expectedParams: []string{"x", "y", "z"}}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + function := stmt.Expression.(*ast.FunctionLiteral) + + if len(function.Parameters) != len(tt.expectedParams) { + t.Errorf("length parameters wrong. want %d, got=%d\n", + len(tt.expectedParams), len(function.Parameters)) + } + + for i, ident := range tt.expectedParams { + testLiteralExpression(t, function.Parameters[i], ident) + } + } +} + +func TestCallExpressionParsing(t *testing.T) { + input := "add(1, 2 * 3, 4 + 5);" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("stmt is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.CallExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.CallExpression. got=%T", + stmt.Expression) + } + + if !testIdentifier(t, exp.Function, "add") { + return + } + + if len(exp.Arguments) != 3 { + t.Fatalf("wrong length of arguments. got=%d", len(exp.Arguments)) + } + + testLiteralExpression(t, exp.Arguments[0], 1) + testInfixExpression(t, exp.Arguments[1], 2, "*", 3) + testInfixExpression(t, exp.Arguments[2], 4, "+", 5) +} + +func TestCallExpressionParameterParsing(t *testing.T) { + tests := []struct { + input string + expectedIdent string + expectedArgs []string + }{ + { + input: "add();", + expectedIdent: "add", + expectedArgs: []string{}, + }, + { + input: "add(1);", + expectedIdent: "add", + expectedArgs: []string{"1"}, + }, + { + input: "add(1, 2 * 3, 4 + 5);", + expectedIdent: "add", + expectedArgs: []string{"1", "(2 * 3)", "(4 + 5)"}, + }, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + exp, ok := stmt.Expression.(*ast.CallExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.CallExpression. got=%T", + stmt.Expression) + } + + if !testIdentifier(t, exp.Function, tt.expectedIdent) { + return + } + + if len(exp.Arguments) != len(tt.expectedArgs) { + t.Fatalf("wrong number of arguments. want=%d, got=%d", + len(tt.expectedArgs), len(exp.Arguments)) + } + + for i, arg := range tt.expectedArgs { + if exp.Arguments[i].String() != arg { + t.Errorf("argument %d wrong. want=%q, got=%q", i, + arg, exp.Arguments[i].String()) + } + } + } +} + +func TestStringLiteralExpression(t *testing.T) { + input := `"hello world";` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + literal, ok := stmt.Expression.(*ast.StringLiteral) + if !ok { + t.Fatalf("exp not *ast.StringLiteral. got=%T", stmt.Expression) + } + + if literal.Value != "hello world" { + t.Errorf("literal.Value not %q. got=%q", "hello world", literal.Value) + } +} + +func TestParsingEmptyArrayLiterals(t *testing.T) { + input := "[]" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + array, ok := stmt.Expression.(*ast.ArrayLiteral) + if !ok { + t.Fatalf("exp not ast.ArrayLiteral. got=%T", stmt.Expression) + } + + if len(array.Elements) != 0 { + t.Errorf("len(array.Elements) not 0. got=%d", len(array.Elements)) + } +} + +func TestParsingArrayLiterals(t *testing.T) { + input := "[1, 2 * 2, 3 + 3]" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + array, ok := stmt.Expression.(*ast.ArrayLiteral) + if !ok { + t.Fatalf("exp not ast.ArrayLiteral. got=%T", stmt.Expression) + } + + if len(array.Elements) != 3 { + t.Fatalf("len(array.Elements) not 3. got=%d", len(array.Elements)) + } + + testIntegerLiteral(t, array.Elements[0], 1) + testInfixExpression(t, array.Elements[1], 2, "*", 2) + testInfixExpression(t, array.Elements[2], 3, "+", 3) +} + +func TestParsingIndexExpressions(t *testing.T) { + input := "myArray[1 + 1]" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + indexExp, ok := stmt.Expression.(*ast.IndexExpression) + if !ok { + t.Fatalf("exp not *ast.IndexExpression. got=%T", stmt.Expression) + } + + if !testIdentifier(t, indexExp.Left, "myArray") { + return + } + + if !testInfixExpression(t, indexExp.Index, 1, "+", 1) { + return + } +} + +func TestParsingEmptyHashLiteral(t *testing.T) { + input := "{}" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + if len(hash.Pairs) != 0 { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } +} + +func TestParsingHashLiteralsStringKeys(t *testing.T) { + input := `{"one": 1, "two": 2, "three": 3}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + expected := map[string]int64{ + "one": 1, + "two": 2, + "three": 3, + } + + if len(hash.Pairs) != len(expected) { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } + + for key, value := range hash.Pairs { + literal, ok := key.(*ast.StringLiteral) + if !ok { + t.Errorf("key is not ast.StringLiteral. got=%T", key) + continue + } + + expectedValue := expected[literal.String()] + testIntegerLiteral(t, value, expectedValue) + } +} + +func TestParsingHashLiteralsBooleanKeys(t *testing.T) { + input := `{true: 1, false: 2}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + expected := map[string]int64{ + "true": 1, + "false": 2, + } + + if len(hash.Pairs) != len(expected) { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } + + for key, value := range hash.Pairs { + boolean, ok := key.(*ast.Boolean) + if !ok { + t.Errorf("key is not ast.BooleanLiteral. got=%T", key) + continue + } + + expectedValue := expected[boolean.String()] + testIntegerLiteral(t, value, expectedValue) + } +} + +func TestParsingHashLiteralsIntegerKeys(t *testing.T) { + input := `{1: 1, 2: 2, 3: 3}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + expected := map[string]int64{ + "1": 1, + "2": 2, + "3": 3, + } + + if len(hash.Pairs) != len(expected) { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } + + for key, value := range hash.Pairs { + integer, ok := key.(*ast.IntegerLiteral) + if !ok { + t.Errorf("key is not ast.IntegerLiteral. got=%T", key) + continue + } + + expectedValue := expected[integer.String()] + + testIntegerLiteral(t, value, expectedValue) + } +} + +func TestParsingHashLiteralsWithExpressions(t *testing.T) { + input := `{"one": 0 + 1, "two": 10 - 8, "three": 15 / 5}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + if len(hash.Pairs) != 3 { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } + + tests := map[string]func(ast.Expression){ + "one": func(e ast.Expression) { + testInfixExpression(t, e, 0, "+", 1) + }, + "two": func(e ast.Expression) { + testInfixExpression(t, e, 10, "-", 8) + }, + "three": func(e ast.Expression) { + testInfixExpression(t, e, 15, "/", 5) + }, + } + + for key, value := range hash.Pairs { + literal, ok := key.(*ast.StringLiteral) + if !ok { + t.Errorf("key is not ast.StringLiteral. got=%T", key) + continue + } + + testFunc, ok := tests[literal.String()] + if !ok { + t.Errorf("No test function for key %q found", literal.String()) + continue + } + + testFunc(value) + } +} + +func TestFunctionLiteralWithName(t *testing.T) { + input := `let myFunction = fn() { };` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Body does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.LetStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.LetStatement. got=%T", + program.Statements[0]) + } + + function, ok := stmt.Value.(*ast.FunctionLiteral) + if !ok { + t.Fatalf("stmt.Value is not ast.FunctionLiteral. got=%T", + stmt.Value) + } + + if function.Name != "myFunction" { + t.Fatalf("function literal name wrong. want 'myFunction', got=%q\n", + function.Name) + } +} + +func testLetStatement(t *testing.T, s ast.Statement, name string) bool { + if s.TokenLiteral() != "let" { + t.Errorf("s.TokenLiteral not 'let'. got=%q", s.TokenLiteral()) + return false + } + + letStmt, ok := s.(*ast.LetStatement) + if !ok { + t.Errorf("s not *ast.LetStatement. got=%T", s) + return false + } + + if letStmt.Name.Value != name { + t.Errorf("letStmt.Name.Value not '%s'. got=%s", name, letStmt.Name.Value) + return false + } + + if letStmt.Name.TokenLiteral() != name { + t.Errorf("letStmt.Name.TokenLiteral() not '%s'. got=%s", + name, letStmt.Name.TokenLiteral()) + return false + } + + return true +} + +func testInfixExpression(t *testing.T, exp ast.Expression, left interface{}, + operator string, right interface{}) bool { + + opExp, ok := exp.(*ast.InfixExpression) + if !ok { + t.Errorf("exp is not ast.InfixExpression. got=%T(%s)", exp, exp) + return false + } + + if !testLiteralExpression(t, opExp.Left, left) { + return false + } + + if opExp.Operator != operator { + t.Errorf("exp.Operator is not '%s'. got=%q", operator, opExp.Operator) + return false + } + + if !testLiteralExpression(t, opExp.Right, right) { + return false + } + + return true +} + +func testLiteralExpression( + t *testing.T, + exp ast.Expression, + expected interface{}, +) bool { + switch v := expected.(type) { + case int: + return testIntegerLiteral(t, exp, int64(v)) + case int64: + return testIntegerLiteral(t, exp, v) + case string: + return testIdentifier(t, exp, v) + case bool: + return testBooleanLiteral(t, exp, v) + } + t.Errorf("type of exp not handled. got=%T", exp) + return false +} + +func testIntegerLiteral(t *testing.T, il ast.Expression, value int64) bool { + integ, ok := il.(*ast.IntegerLiteral) + if !ok { + t.Errorf("il not *ast.IntegerLiteral. got=%T", il) + return false + } + + if integ.Value != value { + t.Errorf("integ.Value not %d. got=%d", value, integ.Value) + return false + } + + if integ.TokenLiteral() != fmt.Sprintf("%d", value) { + t.Errorf("integ.TokenLiteral not %d. got=%s", value, + integ.TokenLiteral()) + return false + } + + return true +} + +func testIdentifier(t *testing.T, exp ast.Expression, value string) bool { + ident, ok := exp.(*ast.Identifier) + if !ok { + t.Errorf("exp not *ast.Identifier. got=%T", exp) + return false + } + + if ident.Value != value { + t.Errorf("ident.Value not %s. got=%s", value, ident.Value) + return false + } + + if ident.TokenLiteral() != value { + t.Errorf("ident.TokenLiteral not %s. got=%s", value, + ident.TokenLiteral()) + return false + } + + return true +} + +func testBooleanLiteral(t *testing.T, exp ast.Expression, value bool) bool { + bo, ok := exp.(*ast.Boolean) + if !ok { + t.Errorf("exp not *ast.Boolean. got=%T", exp) + return false + } + + if bo.Value != value { + t.Errorf("bo.Value not %t. got=%t", value, bo.Value) + return false + } + + if bo.TokenLiteral() != fmt.Sprintf("%t", value) { + t.Errorf("bo.TokenLiteral not %t. got=%s", + value, bo.TokenLiteral()) + return false + } + + return true +} + +func checkParserErrors(t *testing.T, p *Parser) { + errors := p.Errors() + if len(errors) == 0 { + return + } + + t.Errorf("parser has %d errors", len(errors)) + for _, msg := range errors { + t.Errorf("parser error: %q", msg) + } + t.FailNow() +} diff --git a/wcig_code_1_2/09/src/monkey/parser/parser_tracing.go b/wcig_code_1_2/09/src/monkey/parser/parser_tracing.go new file mode 100644 index 0000000..5fc569b --- /dev/null +++ b/wcig_code_1_2/09/src/monkey/parser/parser_tracing.go @@ -0,0 +1,32 @@ +package parser + +import ( + "fmt" + "strings" +) + +var traceLevel int = 0 + +const traceIdentPlaceholder string = "\t" + +func identLevel() string { + return strings.Repeat(traceIdentPlaceholder, traceLevel-1) +} + +func tracePrint(fs string) { + fmt.Printf("%s%s\n", identLevel(), fs) +} + +func incIdent() { traceLevel = traceLevel + 1 } +func decIdent() { traceLevel = traceLevel - 1 } + +func trace(msg string) string { + incIdent() + tracePrint("BEGIN " + msg) + return msg +} + +func untrace(msg string) { + tracePrint("END " + msg) + decIdent() +} diff --git a/wcig_code_1_2/09/src/monkey/repl/repl.go b/wcig_code_1_2/09/src/monkey/repl/repl.go new file mode 100644 index 0000000..dac5563 --- /dev/null +++ b/wcig_code_1_2/09/src/monkey/repl/repl.go @@ -0,0 +1,87 @@ +package repl + +import ( + "bufio" + "fmt" + "io" + "monkey/compiler" + "monkey/lexer" + "monkey/object" + "monkey/parser" + "monkey/vm" +) + +const PROMPT = ">> " + +func Start(in io.Reader, out io.Writer) { + scanner := bufio.NewScanner(in) + + constants := []object.Object{} + globals := make([]object.Object, vm.GlobalsSize) + + symbolTable := compiler.NewSymbolTable() + for i, v := range object.Builtins { + symbolTable.DefineBuiltin(i, v.Name) + } + + for { + fmt.Fprintf(out, PROMPT) + scanned := scanner.Scan() + if !scanned { + return + } + + line := scanner.Text() + l := lexer.New(line) + p := parser.New(l) + + program := p.ParseProgram() + if len(p.Errors()) != 0 { + printParserErrors(out, p.Errors()) + continue + } + + comp := compiler.NewWithState(symbolTable, constants) + err := comp.Compile(program) + if err != nil { + fmt.Fprintf(out, "Woops! Compilation failed:\n %s\n", err) + continue + } + + code := comp.Bytecode() + constants = code.Constants + + machine := vm.NewWithGlobalsStore(code, globals) + err = machine.Run() + if err != nil { + fmt.Fprintf(out, "Woops! Executing bytecode failed:\n %s\n", err) + continue + } + + lastPopped := machine.LastPoppedStackElem() + io.WriteString(out, lastPopped.Inspect()) + io.WriteString(out, "\n") + } +} + +const MONKEY_FACE = ` __,__ + .--. .-" "-. .--. + / .. \/ .-. .-. \/ .. \ + | | '| / Y \ |' | | + | \ \ \ 0 | 0 / / / | + \ '- ,\.-"""""""-./, -' / + ''-' /_ ^ ^ _\ '-'' + | \._ _./ | + \ \ '~' / / + '._ '-=-' _.' + '-----' +` + +func printParserErrors(out io.Writer, errors []string) { + io.WriteString(out, MONKEY_FACE) + io.WriteString(out, "Woops! We ran into some monkey business here!\n") + io.WriteString(out, " parser errors:\n") + for _, msg := range errors { + io.WriteString(out, "\t"+msg+"\n") + } +} diff --git a/wcig_code_1_2/09/src/monkey/token/token.go b/wcig_code_1_2/09/src/monkey/token/token.go new file mode 100644 index 0000000..3d2d2f7 --- /dev/null +++ b/wcig_code_1_2/09/src/monkey/token/token.go @@ -0,0 +1,70 @@ +package token + +type TokenType string + +const ( + ILLEGAL = "ILLEGAL" + EOF = "EOF" + + // Identifiers + literals + IDENT = "IDENT" // add, foobar, x, y, ... + INT = "INT" // 1343456 + STRING = "STRING" // "foobar" + + // Operators + ASSIGN = "=" + PLUS = "+" + MINUS = "-" + BANG = "!" + ASTERISK = "*" + SLASH = "/" + + LT = "<" + GT = ">" + + EQ = "==" + NOT_EQ = "!=" + + // Delimiters + COMMA = "," + SEMICOLON = ";" + COLON = ":" + + LPAREN = "(" + RPAREN = ")" + LBRACE = "{" + RBRACE = "}" + LBRACKET = "[" + RBRACKET = "]" + + // Keywords + FUNCTION = "FUNCTION" + LET = "LET" + TRUE = "TRUE" + FALSE = "FALSE" + IF = "IF" + ELSE = "ELSE" + RETURN = "RETURN" +) + +type Token struct { + Type TokenType + Literal string +} + +var keywords = map[string]TokenType{ + "fn": FUNCTION, + "let": LET, + "true": TRUE, + "false": FALSE, + "if": IF, + "else": ELSE, + "return": RETURN, +} + +func LookupIdent(ident string) TokenType { + if tok, ok := keywords[ident]; ok { + return tok + } + return IDENT +} diff --git a/wcig_code_1_2/09/src/monkey/vm/frame.go b/wcig_code_1_2/09/src/monkey/vm/frame.go new file mode 100644 index 0000000..2a11df5 --- /dev/null +++ b/wcig_code_1_2/09/src/monkey/vm/frame.go @@ -0,0 +1,26 @@ +package vm + +import ( + "monkey/code" + "monkey/object" +) + +type Frame struct { + cl *object.Closure + ip int + basePointer int +} + +func NewFrame(cl *object.Closure, basePointer int) *Frame { + f := &Frame{ + cl: cl, + ip: -1, + basePointer: basePointer, + } + + return f +} + +func (f *Frame) Instructions() code.Instructions { + return f.cl.Fn.Instructions +} diff --git a/wcig_code_1_2/09/src/monkey/vm/vm.go b/wcig_code_1_2/09/src/monkey/vm/vm.go new file mode 100644 index 0000000..3e25113 --- /dev/null +++ b/wcig_code_1_2/09/src/monkey/vm/vm.go @@ -0,0 +1,581 @@ +package vm + +import ( + "fmt" + "monkey/code" + "monkey/compiler" + "monkey/object" +) + +const StackSize = 2048 +const GlobalsSize = 65536 +const MaxFrames = 1024 + +var True = &object.Boolean{Value: true} +var False = &object.Boolean{Value: false} +var Null = &object.Null{} + +type VM struct { + constants []object.Object + + stack []object.Object + sp int // Always points to the next value. Top of stack is stack[sp-1] + + globals []object.Object + + frames []*Frame + framesIndex int +} + +func New(bytecode *compiler.Bytecode) *VM { + mainFn := &object.CompiledFunction{Instructions: bytecode.Instructions} + mainClosure := &object.Closure{Fn: mainFn} + mainFrame := NewFrame(mainClosure, 0) + + frames := make([]*Frame, MaxFrames) + frames[0] = mainFrame + + return &VM{ + constants: bytecode.Constants, + + stack: make([]object.Object, StackSize), + sp: 0, + + globals: make([]object.Object, GlobalsSize), + + frames: frames, + framesIndex: 1, + } +} + +func NewWithGlobalsStore(bytecode *compiler.Bytecode, s []object.Object) *VM { + vm := New(bytecode) + vm.globals = s + return vm +} + +func (vm *VM) LastPoppedStackElem() object.Object { + return vm.stack[vm.sp] +} + +func (vm *VM) Run() error { + var ip int + var ins code.Instructions + var op code.Opcode + + for vm.currentFrame().ip < len(vm.currentFrame().Instructions())-1 { + vm.currentFrame().ip++ + + ip = vm.currentFrame().ip + ins = vm.currentFrame().Instructions() + op = code.Opcode(ins[ip]) + + switch op { + case code.OpConstant: + constIndex := code.ReadUint16(ins[ip+1:]) + vm.currentFrame().ip += 2 + + err := vm.push(vm.constants[constIndex]) + if err != nil { + return err + } + + case code.OpPop: + vm.pop() + + case code.OpAdd, code.OpSub, code.OpMul, code.OpDiv: + err := vm.executeBinaryOperation(op) + if err != nil { + return err + } + + case code.OpTrue: + err := vm.push(True) + if err != nil { + return err + } + + case code.OpFalse: + err := vm.push(False) + if err != nil { + return err + } + + case code.OpEqual, code.OpNotEqual, code.OpGreaterThan: + err := vm.executeComparison(op) + if err != nil { + return err + } + + case code.OpBang: + err := vm.executeBangOperator() + if err != nil { + return err + } + + case code.OpMinus: + err := vm.executeMinusOperator() + if err != nil { + return err + } + + case code.OpJump: + pos := int(code.ReadUint16(ins[ip+1:])) + vm.currentFrame().ip = pos - 1 + + case code.OpJumpNotTruthy: + pos := int(code.ReadUint16(ins[ip+1:])) + vm.currentFrame().ip += 2 + + condition := vm.pop() + if !isTruthy(condition) { + vm.currentFrame().ip = pos - 1 + } + + case code.OpNull: + err := vm.push(Null) + if err != nil { + return err + } + + case code.OpSetGlobal: + globalIndex := code.ReadUint16(ins[ip+1:]) + vm.currentFrame().ip += 2 + + vm.globals[globalIndex] = vm.pop() + + case code.OpGetGlobal: + globalIndex := code.ReadUint16(ins[ip+1:]) + vm.currentFrame().ip += 2 + + err := vm.push(vm.globals[globalIndex]) + if err != nil { + return err + } + + case code.OpArray: + numElements := int(code.ReadUint16(ins[ip+1:])) + vm.currentFrame().ip += 2 + + array := vm.buildArray(vm.sp-numElements, vm.sp) + vm.sp = vm.sp - numElements + + err := vm.push(array) + if err != nil { + return err + } + + case code.OpHash: + numElements := int(code.ReadUint16(ins[ip+1:])) + vm.currentFrame().ip += 2 + + hash, err := vm.buildHash(vm.sp-numElements, vm.sp) + if err != nil { + return err + } + vm.sp = vm.sp - numElements + + err = vm.push(hash) + if err != nil { + return err + } + + case code.OpIndex: + index := vm.pop() + left := vm.pop() + + err := vm.executeIndexExpression(left, index) + if err != nil { + return err + } + + case code.OpCall: + numArgs := code.ReadUint8(ins[ip+1:]) + vm.currentFrame().ip += 1 + + err := vm.executeCall(int(numArgs)) + if err != nil { + return err + } + + case code.OpReturnValue: + returnValue := vm.pop() + + frame := vm.popFrame() + vm.sp = frame.basePointer - 1 + + err := vm.push(returnValue) + if err != nil { + return err + } + + case code.OpReturn: + frame := vm.popFrame() + vm.sp = frame.basePointer - 1 + + err := vm.push(Null) + if err != nil { + return err + } + + case code.OpSetLocal: + localIndex := code.ReadUint8(ins[ip+1:]) + vm.currentFrame().ip += 1 + + frame := vm.currentFrame() + + vm.stack[frame.basePointer+int(localIndex)] = vm.pop() + + case code.OpGetLocal: + localIndex := code.ReadUint8(ins[ip+1:]) + vm.currentFrame().ip += 1 + + frame := vm.currentFrame() + + err := vm.push(vm.stack[frame.basePointer+int(localIndex)]) + if err != nil { + return err + } + + case code.OpGetBuiltin: + builtinIndex := code.ReadUint8(ins[ip+1:]) + vm.currentFrame().ip += 1 + + definition := object.Builtins[builtinIndex] + + err := vm.push(definition.Builtin) + if err != nil { + return err + } + + case code.OpClosure: + constIndex := code.ReadUint16(ins[ip+1:]) + numFree := code.ReadUint8(ins[ip+3:]) + vm.currentFrame().ip += 3 + + err := vm.pushClosure(int(constIndex), int(numFree)) + if err != nil { + return err + } + + case code.OpGetFree: + freeIndex := code.ReadUint8(ins[ip+1:]) + vm.currentFrame().ip += 1 + + currentClosure := vm.currentFrame().cl + err := vm.push(currentClosure.Free[freeIndex]) + if err != nil { + return err + } + + case code.OpCurrentClosure: + currentClosure := vm.currentFrame().cl + err := vm.push(currentClosure) + if err != nil { + return err + } + } + } + + return nil +} + +func (vm *VM) push(o object.Object) error { + if vm.sp >= StackSize { + return fmt.Errorf("stack overflow") + } + + vm.stack[vm.sp] = o + vm.sp++ + + return nil +} + +func (vm *VM) pop() object.Object { + o := vm.stack[vm.sp-1] + vm.sp-- + return o +} + +func (vm *VM) executeBinaryOperation(op code.Opcode) error { + right := vm.pop() + left := vm.pop() + + leftType := left.Type() + rightType := right.Type() + + switch { + case leftType == object.INTEGER_OBJ && rightType == object.INTEGER_OBJ: + return vm.executeBinaryIntegerOperation(op, left, right) + case leftType == object.STRING_OBJ && rightType == object.STRING_OBJ: + return vm.executeBinaryStringOperation(op, left, right) + default: + return fmt.Errorf("unsupported types for binary operation: %s %s", + leftType, rightType) + } +} + +func (vm *VM) executeBinaryIntegerOperation( + op code.Opcode, + left, right object.Object, +) error { + leftValue := left.(*object.Integer).Value + rightValue := right.(*object.Integer).Value + + var result int64 + + switch op { + case code.OpAdd: + result = leftValue + rightValue + case code.OpSub: + result = leftValue - rightValue + case code.OpMul: + result = leftValue * rightValue + case code.OpDiv: + result = leftValue / rightValue + default: + return fmt.Errorf("unknown integer operator: %d", op) + } + + return vm.push(&object.Integer{Value: result}) +} + +func (vm *VM) executeComparison(op code.Opcode) error { + right := vm.pop() + left := vm.pop() + + if left.Type() == object.INTEGER_OBJ && right.Type() == object.INTEGER_OBJ { + return vm.executeIntegerComparison(op, left, right) + } + + switch op { + case code.OpEqual: + return vm.push(nativeBoolToBooleanObject(right == left)) + case code.OpNotEqual: + return vm.push(nativeBoolToBooleanObject(right != left)) + default: + return fmt.Errorf("unknown operator: %d (%s %s)", + op, left.Type(), right.Type()) + } +} + +func (vm *VM) executeIntegerComparison( + op code.Opcode, + left, right object.Object, +) error { + leftValue := left.(*object.Integer).Value + rightValue := right.(*object.Integer).Value + + switch op { + case code.OpEqual: + return vm.push(nativeBoolToBooleanObject(rightValue == leftValue)) + case code.OpNotEqual: + return vm.push(nativeBoolToBooleanObject(rightValue != leftValue)) + case code.OpGreaterThan: + return vm.push(nativeBoolToBooleanObject(leftValue > rightValue)) + default: + return fmt.Errorf("unknown operator: %d", op) + } +} + +func (vm *VM) executeBangOperator() error { + operand := vm.pop() + + switch operand { + case True: + return vm.push(False) + case False: + return vm.push(True) + case Null: + return vm.push(True) + default: + return vm.push(False) + } +} + +func (vm *VM) executeMinusOperator() error { + operand := vm.pop() + + if operand.Type() != object.INTEGER_OBJ { + return fmt.Errorf("unsupported type for negation: %s", operand.Type()) + } + + value := operand.(*object.Integer).Value + return vm.push(&object.Integer{Value: -value}) +} + +func (vm *VM) executeBinaryStringOperation( + op code.Opcode, + left, right object.Object, +) error { + if op != code.OpAdd { + return fmt.Errorf("unknown string operator: %d", op) + } + + leftValue := left.(*object.String).Value + rightValue := right.(*object.String).Value + + return vm.push(&object.String{Value: leftValue + rightValue}) +} + +func (vm *VM) buildArray(startIndex, endIndex int) object.Object { + elements := make([]object.Object, endIndex-startIndex) + + for i := startIndex; i < endIndex; i++ { + elements[i-startIndex] = vm.stack[i] + } + + return &object.Array{Elements: elements} +} + +func (vm *VM) buildHash(startIndex, endIndex int) (object.Object, error) { + hashedPairs := make(map[object.HashKey]object.HashPair) + + for i := startIndex; i < endIndex; i += 2 { + key := vm.stack[i] + value := vm.stack[i+1] + + pair := object.HashPair{Key: key, Value: value} + + hashKey, ok := key.(object.Hashable) + if !ok { + return nil, fmt.Errorf("unusable as hash key: %s", key.Type()) + } + + hashedPairs[hashKey.HashKey()] = pair + } + + return &object.Hash{Pairs: hashedPairs}, nil +} + +func (vm *VM) executeIndexExpression(left, index object.Object) error { + switch { + case left.Type() == object.ARRAY_OBJ && index.Type() == object.INTEGER_OBJ: + return vm.executeArrayIndex(left, index) + case left.Type() == object.HASH_OBJ: + return vm.executeHashIndex(left, index) + default: + return fmt.Errorf("index operator not supported: %s", left.Type()) + } +} + +func (vm *VM) executeArrayIndex(array, index object.Object) error { + arrayObject := array.(*object.Array) + i := index.(*object.Integer).Value + max := int64(len(arrayObject.Elements) - 1) + + if i < 0 || i > max { + return vm.push(Null) + } + + return vm.push(arrayObject.Elements[i]) +} + +func (vm *VM) executeHashIndex(hash, index object.Object) error { + hashObject := hash.(*object.Hash) + + key, ok := index.(object.Hashable) + if !ok { + return fmt.Errorf("unusable as hash key: %s", index.Type()) + } + + pair, ok := hashObject.Pairs[key.HashKey()] + if !ok { + return vm.push(Null) + } + + return vm.push(pair.Value) +} + +func (vm *VM) currentFrame() *Frame { + return vm.frames[vm.framesIndex-1] +} + +func (vm *VM) pushFrame(f *Frame) { + vm.frames[vm.framesIndex] = f + vm.framesIndex++ +} + +func (vm *VM) popFrame() *Frame { + vm.framesIndex-- + return vm.frames[vm.framesIndex] +} + +func (vm *VM) executeCall(numArgs int) error { + callee := vm.stack[vm.sp-1-numArgs] + switch callee := callee.(type) { + case *object.Closure: + return vm.callClosure(callee, numArgs) + case *object.Builtin: + return vm.callBuiltin(callee, numArgs) + default: + return fmt.Errorf("calling non-closure and non-builtin") + } +} + +func (vm *VM) callClosure(cl *object.Closure, numArgs int) error { + if numArgs != cl.Fn.NumParameters { + return fmt.Errorf("wrong number of arguments: want=%d, got=%d", + cl.Fn.NumParameters, numArgs) + } + + frame := NewFrame(cl, vm.sp-numArgs) + vm.pushFrame(frame) + + vm.sp = frame.basePointer + cl.Fn.NumLocals + + return nil +} + +func (vm *VM) callBuiltin(builtin *object.Builtin, numArgs int) error { + args := vm.stack[vm.sp-numArgs : vm.sp] + + result := builtin.Fn(args...) + vm.sp = vm.sp - numArgs - 1 + + if result != nil { + vm.push(result) + } else { + vm.push(Null) + } + + return nil +} + +func (vm *VM) pushClosure(constIndex, numFree int) error { + constant := vm.constants[constIndex] + function, ok := constant.(*object.CompiledFunction) + if !ok { + return fmt.Errorf("not a function: %+v", constant) + } + + free := make([]object.Object, numFree) + for i := 0; i < numFree; i++ { + free[i] = vm.stack[vm.sp-numFree+i] + } + vm.sp = vm.sp - numFree + + closure := &object.Closure{Fn: function, Free: free} + return vm.push(closure) +} + +func nativeBoolToBooleanObject(input bool) *object.Boolean { + if input { + return True + } + return False +} + +func isTruthy(obj object.Object) bool { + switch obj := obj.(type) { + + case *object.Boolean: + return obj.Value + + case *object.Null: + return false + + default: + return true + } +} diff --git a/wcig_code_1_2/09/src/monkey/vm/vm_test.go b/wcig_code_1_2/09/src/monkey/vm/vm_test.go new file mode 100644 index 0000000..544dd67 --- /dev/null +++ b/wcig_code_1_2/09/src/monkey/vm/vm_test.go @@ -0,0 +1,761 @@ +package vm + +import ( + "fmt" + "monkey/ast" + "monkey/compiler" + "monkey/lexer" + "monkey/object" + "monkey/parser" + "testing" +) + +func TestIntegerArithmetic(t *testing.T) { + tests := []vmTestCase{ + {"1", 1}, + {"2", 2}, + {"1 + 2", 3}, + {"1 - 2", -1}, + {"1 * 2", 2}, + {"4 / 2", 2}, + {"50 / 2 * 2 + 10 - 5", 55}, + {"5 * (2 + 10)", 60}, + {"5 + 5 + 5 + 5 - 10", 10}, + {"2 * 2 * 2 * 2 * 2", 32}, + {"5 * 2 + 10", 20}, + {"5 + 2 * 10", 25}, + {"5 * (2 + 10)", 60}, + {"-5", -5}, + {"-10", -10}, + {"-50 + 100 + -50", 0}, + {"(5 + 10 * 2 + 15 / 3) * 2 + -10", 50}, + } + + runVmTests(t, tests) +} + +func TestBooleanExpressions(t *testing.T) { + tests := []vmTestCase{ + {"true", true}, + {"false", false}, + {"1 < 2", true}, + {"1 > 2", false}, + {"1 < 1", false}, + {"1 > 1", false}, + {"1 == 1", true}, + {"1 != 1", false}, + {"1 == 2", false}, + {"1 != 2", true}, + {"true == true", true}, + {"false == false", true}, + {"true == false", false}, + {"true != false", true}, + {"false != true", true}, + {"(1 < 2) == true", true}, + {"(1 < 2) == false", false}, + {"(1 > 2) == true", false}, + {"(1 > 2) == false", true}, + {"!true", false}, + {"!false", true}, + {"!5", false}, + {"!!true", true}, + {"!!false", false}, + {"!!5", true}, + {"!(if (false) { 5; })", true}, + } + + runVmTests(t, tests) +} + +func TestConditionals(t *testing.T) { + tests := []vmTestCase{ + {"if (true) { 10 }", 10}, + {"if (true) { 10 } else { 20 }", 10}, + {"if (false) { 10 } else { 20 } ", 20}, + {"if (1) { 10 }", 10}, + {"if (1 < 2) { 10 }", 10}, + {"if (1 < 2) { 10 } else { 20 }", 10}, + {"if (1 > 2) { 10 } else { 20 }", 20}, + {"if (1 > 2) { 10 }", Null}, + {"if (false) { 10 }", Null}, + {"if ((if (false) { 10 })) { 10 } else { 20 }", 20}, + } + + runVmTests(t, tests) +} + +func TestGlobalLetStatements(t *testing.T) { + tests := []vmTestCase{ + {"let one = 1; one", 1}, + {"let one = 1; let two = 2; one + two", 3}, + {"let one = 1; let two = one + one; one + two", 3}, + } + + runVmTests(t, tests) +} + +func TestStringExpressions(t *testing.T) { + tests := []vmTestCase{ + {`"monkey"`, "monkey"}, + {`"mon" + "key"`, "monkey"}, + {`"mon" + "key" + "banana"`, "monkeybanana"}, + } + + runVmTests(t, tests) +} + +func TestArrayLiterals(t *testing.T) { + tests := []vmTestCase{ + {"[]", []int{}}, + {"[1, 2, 3]", []int{1, 2, 3}}, + {"[1 + 2, 3 * 4, 5 + 6]", []int{3, 12, 11}}, + } + + runVmTests(t, tests) +} + +func TestHashLiterals(t *testing.T) { + tests := []vmTestCase{ + { + "{}", map[object.HashKey]int64{}, + }, + { + "{1: 2, 2: 3}", + map[object.HashKey]int64{ + (&object.Integer{Value: 1}).HashKey(): 2, + (&object.Integer{Value: 2}).HashKey(): 3, + }, + }, + { + "{1 + 1: 2 * 2, 3 + 3: 4 * 4}", + map[object.HashKey]int64{ + (&object.Integer{Value: 2}).HashKey(): 4, + (&object.Integer{Value: 6}).HashKey(): 16, + }, + }, + } + + runVmTests(t, tests) +} + +func TestIndexExpressions(t *testing.T) { + tests := []vmTestCase{ + {"[1, 2, 3][1]", 2}, + {"[1, 2, 3][0 + 2]", 3}, + {"[[1, 1, 1]][0][0]", 1}, + {"[][0]", Null}, + {"[1, 2, 3][99]", Null}, + {"[1][-1]", Null}, + {"{1: 1, 2: 2}[1]", 1}, + {"{1: 1, 2: 2}[2]", 2}, + {"{1: 1}[0]", Null}, + {"{}[0]", Null}, + } + + runVmTests(t, tests) +} + +func TestCallingFunctionsWithoutArguments(t *testing.T) { + tests := []vmTestCase{ + { + input: ` + let fivePlusTen = fn() { 5 + 10; }; + fivePlusTen(); + `, + expected: 15, + }, + { + input: ` + let one = fn() { 1; }; + let two = fn() { 2; }; + one() + two() + `, + expected: 3, + }, + { + input: ` + let a = fn() { 1 }; + let b = fn() { a() + 1 }; + let c = fn() { b() + 1 }; + c(); + `, + expected: 3, + }, + } + + runVmTests(t, tests) +} + +func TestFunctionsWithReturnStatement(t *testing.T) { + tests := []vmTestCase{ + { + input: ` + let earlyExit = fn() { return 99; 100; }; + earlyExit(); + `, + expected: 99, + }, + { + input: ` + let earlyExit = fn() { return 99; return 100; }; + earlyExit(); + `, + expected: 99, + }, + } + + runVmTests(t, tests) +} + +func TestFunctionsWithoutReturnValue(t *testing.T) { + tests := []vmTestCase{ + { + input: ` + let noReturn = fn() { }; + noReturn(); + `, + expected: Null, + }, + { + input: ` + let noReturn = fn() { }; + let noReturnTwo = fn() { noReturn(); }; + noReturn(); + noReturnTwo(); + `, + expected: Null, + }, + } + + runVmTests(t, tests) +} + +func TestFirstClassFunctions(t *testing.T) { + tests := []vmTestCase{ + { + input: ` + let returnsOne = fn() { 1; }; + let returnsOneReturner = fn() { returnsOne; }; + returnsOneReturner()(); + `, + expected: 1, + }, + { + input: ` + let returnsOneReturner = fn() { + let returnsOne = fn() { 1; }; + returnsOne; + }; + returnsOneReturner()(); + `, + expected: 1, + }, + } + + runVmTests(t, tests) +} + +func TestCallingFunctionsWithBindings(t *testing.T) { + tests := []vmTestCase{ + { + input: ` + let one = fn() { let one = 1; one }; + one(); + `, + expected: 1, + }, + { + input: ` + let oneAndTwo = fn() { let one = 1; let two = 2; one + two; }; + oneAndTwo(); + `, + expected: 3, + }, + { + input: ` + let oneAndTwo = fn() { let one = 1; let two = 2; one + two; }; + let threeAndFour = fn() { let three = 3; let four = 4; three + four; }; + oneAndTwo() + threeAndFour(); + `, + expected: 10, + }, + { + input: ` + let firstFoobar = fn() { let foobar = 50; foobar; }; + let secondFoobar = fn() { let foobar = 100; foobar; }; + firstFoobar() + secondFoobar(); + `, + expected: 150, + }, + { + input: ` + let globalSeed = 50; + let minusOne = fn() { + let num = 1; + globalSeed - num; + } + let minusTwo = fn() { + let num = 2; + globalSeed - num; + } + minusOne() + minusTwo(); + `, + expected: 97, + }, + } + + runVmTests(t, tests) +} + +func TestCallingFunctionsWithArgumentsAndBindings(t *testing.T) { + tests := []vmTestCase{ + { + input: ` + let identity = fn(a) { a; }; + identity(4); + `, + expected: 4, + }, + { + input: ` + let sum = fn(a, b) { a + b; }; + sum(1, 2); + `, + expected: 3, + }, + { + input: ` + let sum = fn(a, b) { + let c = a + b; + c; + }; + sum(1, 2); + `, + expected: 3, + }, + { + input: ` + let sum = fn(a, b) { + let c = a + b; + c; + }; + sum(1, 2) + sum(3, 4);`, + expected: 10, + }, + { + input: ` + let sum = fn(a, b) { + let c = a + b; + c; + }; + let outer = fn() { + sum(1, 2) + sum(3, 4); + }; + outer(); + `, + expected: 10, + }, + { + input: ` + let globalNum = 10; + + let sum = fn(a, b) { + let c = a + b; + c + globalNum; + }; + + let outer = fn() { + sum(1, 2) + sum(3, 4) + globalNum; + }; + + outer() + globalNum; + `, + expected: 50, + }, + } + + runVmTests(t, tests) +} + +func TestCallingFunctionsWithWrongArguments(t *testing.T) { + tests := []vmTestCase{ + { + input: `fn() { 1; }(1);`, + expected: `wrong number of arguments: want=0, got=1`, + }, + { + input: `fn(a) { a; }();`, + expected: `wrong number of arguments: want=1, got=0`, + }, + { + input: `fn(a, b) { a + b; }(1);`, + expected: `wrong number of arguments: want=2, got=1`, + }, + } + + for _, tt := range tests { + program := parse(tt.input) + + comp := compiler.New() + err := comp.Compile(program) + if err != nil { + t.Fatalf("compiler error: %s", err) + } + + vm := New(comp.Bytecode()) + err = vm.Run() + if err == nil { + t.Fatalf("expected VM error but resulted in none.") + } + + if err.Error() != tt.expected { + t.Fatalf("wrong VM error: want=%q, got=%q", tt.expected, err) + } + } +} + +func TestBuiltinFunctions(t *testing.T) { + tests := []vmTestCase{ + {`len("")`, 0}, + {`len("four")`, 4}, + {`len("hello world")`, 11}, + { + `len(1)`, + &object.Error{ + Message: "argument to `len` not supported, got INTEGER", + }, + }, + {`len("one", "two")`, + &object.Error{ + Message: "wrong number of arguments. got=2, want=1", + }, + }, + {`len([1, 2, 3])`, 3}, + {`len([])`, 0}, + {`puts("hello", "world!")`, Null}, + {`first([1, 2, 3])`, 1}, + {`first([])`, Null}, + {`first(1)`, + &object.Error{ + Message: "argument to `first` must be ARRAY, got INTEGER", + }, + }, + {`last([1, 2, 3])`, 3}, + {`last([])`, Null}, + {`last(1)`, + &object.Error{ + Message: "argument to `last` must be ARRAY, got INTEGER", + }, + }, + {`rest([1, 2, 3])`, []int{2, 3}}, + {`rest([])`, Null}, + {`push([], 1)`, []int{1}}, + {`push(1, 1)`, + &object.Error{ + Message: "argument to `push` must be ARRAY, got INTEGER", + }, + }, + } + + runVmTests(t, tests) +} + +func TestClosures(t *testing.T) { + tests := []vmTestCase{ + { + input: ` + let newClosure = fn(a) { + fn() { a; }; + }; + let closure = newClosure(99); + closure(); + `, + expected: 99, + }, + { + input: ` + let newAdder = fn(a, b) { + fn(c) { a + b + c }; + }; + let adder = newAdder(1, 2); + adder(8); + `, + expected: 11, + }, + { + input: ` + let newAdder = fn(a, b) { + let c = a + b; + fn(d) { c + d }; + }; + let adder = newAdder(1, 2); + adder(8); + `, + expected: 11, + }, + { + input: ` + let newAdderOuter = fn(a, b) { + let c = a + b; + fn(d) { + let e = d + c; + fn(f) { e + f; }; + }; + }; + let newAdderInner = newAdderOuter(1, 2) + let adder = newAdderInner(3); + adder(8); + `, + expected: 14, + }, + { + input: ` + let a = 1; + let newAdderOuter = fn(b) { + fn(c) { + fn(d) { a + b + c + d }; + }; + }; + let newAdderInner = newAdderOuter(2) + let adder = newAdderInner(3); + adder(8); + `, + expected: 14, + }, + { + input: ` + let newClosure = fn(a, b) { + let one = fn() { a; }; + let two = fn() { b; }; + fn() { one() + two(); }; + }; + let closure = newClosure(9, 90); + closure(); + `, + expected: 99, + }, + } + + runVmTests(t, tests) +} + +func TestRecursiveFunctions(t *testing.T) { + tests := []vmTestCase{ + { + input: ` + let countDown = fn(x) { + if (x == 0) { + return 0; + } else { + countDown(x - 1); + } + }; + countDown(1); + `, + expected: 0, + }, + { + input: ` + let countDown = fn(x) { + if (x == 0) { + return 0; + } else { + countDown(x - 1); + } + }; + let wrapper = fn() { + countDown(1); + }; + wrapper(); + `, + expected: 0, + }, + { + input: ` + let wrapper = fn() { + let countDown = fn(x) { + if (x == 0) { + return 0; + } else { + countDown(x - 1); + } + }; + countDown(1); + }; + wrapper(); + `, + expected: 0, + }, + } + + runVmTests(t, tests) +} + +type vmTestCase struct { + input string + expected interface{} +} + +func runVmTests(t *testing.T, tests []vmTestCase) { + t.Helper() + + for _, tt := range tests { + program := parse(tt.input) + + comp := compiler.New() + err := comp.Compile(program) + if err != nil { + t.Fatalf("compiler error: %s", err) + } + + vm := New(comp.Bytecode()) + err = vm.Run() + if err != nil { + t.Fatalf("vm error: %s", err) + } + + stackElem := vm.LastPoppedStackElem() + + testExpectedObject(t, tt.expected, stackElem) + } +} + +func parse(input string) *ast.Program { + l := lexer.New(input) + p := parser.New(l) + return p.ParseProgram() +} + +func testExpectedObject( + t *testing.T, + expected interface{}, + actual object.Object, +) { + t.Helper() + + switch expected := expected.(type) { + case int: + err := testIntegerObject(int64(expected), actual) + if err != nil { + t.Errorf("testIntegerObject failed: %s", err) + } + + case bool: + err := testBooleanObject(bool(expected), actual) + if err != nil { + t.Errorf("testBooleanObject failed: %s", err) + } + + case *object.Null: + if actual != Null { + t.Errorf("object is not Null: %T (%+v)", actual, actual) + } + + case string: + err := testStringObject(expected, actual) + if err != nil { + t.Errorf("testStringObject failed: %s", err) + } + + case []int: + array, ok := actual.(*object.Array) + if !ok { + t.Errorf("object not Array: %T (%+v)", actual, actual) + return + } + + if len(array.Elements) != len(expected) { + t.Errorf("wrong num of elements. want=%d, got=%d", + len(expected), len(array.Elements)) + return + } + + for i, expectedElem := range expected { + err := testIntegerObject(int64(expectedElem), array.Elements[i]) + if err != nil { + t.Errorf("testIntegerObject failed: %s", err) + } + } + + case map[object.HashKey]int64: + hash, ok := actual.(*object.Hash) + if !ok { + t.Errorf("object is not Hash. got=%T (%+v)", actual, actual) + return + } + + if len(hash.Pairs) != len(expected) { + t.Errorf("hash has wrong number of Pairs. want=%d, got=%d", + len(expected), len(hash.Pairs)) + return + } + + for expectedKey, expectedValue := range expected { + pair, ok := hash.Pairs[expectedKey] + if !ok { + t.Errorf("no pair for given key in Pairs") + } + + err := testIntegerObject(expectedValue, pair.Value) + if err != nil { + t.Errorf("testIntegerObject failed: %s", err) + } + } + + case *object.Error: + errObj, ok := actual.(*object.Error) + if !ok { + t.Errorf("object is not Error: %T (%+v)", actual, actual) + return + } + if errObj.Message != expected.Message { + t.Errorf("wrong error message. expected=%q, got=%q", + expected.Message, errObj.Message) + } + } +} + +func testIntegerObject(expected int64, actual object.Object) error { + result, ok := actual.(*object.Integer) + if !ok { + return fmt.Errorf("object is not Integer. got=%T (%+v)", + actual, actual) + } + + if result.Value != expected { + return fmt.Errorf("object has wrong value. got=%d, want=%d", + result.Value, expected) + } + + return nil +} + +func testBooleanObject(expected bool, actual object.Object) error { + result, ok := actual.(*object.Boolean) + if !ok { + return fmt.Errorf("object is not Boolean. got=%T (%+v)", + actual, actual) + } + + if result.Value != expected { + return fmt.Errorf("object has wrong value. got=%t, want=%t", + result.Value, expected) + } + + return nil +} + +func testStringObject(expected string, actual object.Object) error { + result, ok := actual.(*object.String) + if !ok { + return fmt.Errorf("object is not String. got=%T (%+v)", + actual, actual) + } + + if result.Value != expected { + return fmt.Errorf("object has wrong value. got=%q, want=%q", + result.Value, expected) + } + + return nil +} diff --git a/wcig_code_1_2/10/.envrc b/wcig_code_1_2/10/.envrc new file mode 100644 index 0000000..fb65ef4 --- /dev/null +++ b/wcig_code_1_2/10/.envrc @@ -0,0 +1 @@ +export GOPATH=$(pwd) diff --git a/wcig_code_1_2/10/src/monkey/ast/ast.go b/wcig_code_1_2/10/src/monkey/ast/ast.go new file mode 100644 index 0000000..fb9d497 --- /dev/null +++ b/wcig_code_1_2/10/src/monkey/ast/ast.go @@ -0,0 +1,344 @@ +package ast + +import ( + "bytes" + "fmt" + "monkey/token" + "strings" +) + +// The base Node interface +type Node interface { + TokenLiteral() string + String() string +} + +// All statement nodes implement this +type Statement interface { + Node + statementNode() +} + +// All expression nodes implement this +type Expression interface { + Node + expressionNode() +} + +type Program struct { + Statements []Statement +} + +func (p *Program) TokenLiteral() string { + if len(p.Statements) > 0 { + return p.Statements[0].TokenLiteral() + } else { + return "" + } +} + +func (p *Program) String() string { + var out bytes.Buffer + + for _, s := range p.Statements { + out.WriteString(s.String()) + } + + return out.String() +} + +// Statements +type LetStatement struct { + Token token.Token // the token.LET token + Name *Identifier + Value Expression +} + +func (ls *LetStatement) statementNode() {} +func (ls *LetStatement) TokenLiteral() string { return ls.Token.Literal } +func (ls *LetStatement) String() string { + var out bytes.Buffer + + out.WriteString(ls.TokenLiteral() + " ") + out.WriteString(ls.Name.String()) + out.WriteString(" = ") + + if ls.Value != nil { + out.WriteString(ls.Value.String()) + } + + out.WriteString(";") + + return out.String() +} + +type ReturnStatement struct { + Token token.Token // the 'return' token + ReturnValue Expression +} + +func (rs *ReturnStatement) statementNode() {} +func (rs *ReturnStatement) TokenLiteral() string { return rs.Token.Literal } +func (rs *ReturnStatement) String() string { + var out bytes.Buffer + + out.WriteString(rs.TokenLiteral() + " ") + + if rs.ReturnValue != nil { + out.WriteString(rs.ReturnValue.String()) + } + + out.WriteString(";") + + return out.String() +} + +type ExpressionStatement struct { + Token token.Token // the first token of the expression + Expression Expression +} + +func (es *ExpressionStatement) statementNode() {} +func (es *ExpressionStatement) TokenLiteral() string { return es.Token.Literal } +func (es *ExpressionStatement) String() string { + if es.Expression != nil { + return es.Expression.String() + } + return "" +} + +type BlockStatement struct { + Token token.Token // the { token + Statements []Statement +} + +func (bs *BlockStatement) statementNode() {} +func (bs *BlockStatement) TokenLiteral() string { return bs.Token.Literal } +func (bs *BlockStatement) String() string { + var out bytes.Buffer + + for _, s := range bs.Statements { + out.WriteString(s.String()) + } + + return out.String() +} + +// Expressions +type Identifier struct { + Token token.Token // the token.IDENT token + Value string +} + +func (i *Identifier) expressionNode() {} +func (i *Identifier) TokenLiteral() string { return i.Token.Literal } +func (i *Identifier) String() string { return i.Value } + +type Boolean struct { + Token token.Token + Value bool +} + +func (b *Boolean) expressionNode() {} +func (b *Boolean) TokenLiteral() string { return b.Token.Literal } +func (b *Boolean) String() string { return b.Token.Literal } + +type IntegerLiteral struct { + Token token.Token + Value int64 +} + +func (il *IntegerLiteral) expressionNode() {} +func (il *IntegerLiteral) TokenLiteral() string { return il.Token.Literal } +func (il *IntegerLiteral) String() string { return il.Token.Literal } + +type PrefixExpression struct { + Token token.Token // The prefix token, e.g. ! + Operator string + Right Expression +} + +func (pe *PrefixExpression) expressionNode() {} +func (pe *PrefixExpression) TokenLiteral() string { return pe.Token.Literal } +func (pe *PrefixExpression) String() string { + var out bytes.Buffer + + out.WriteString("(") + out.WriteString(pe.Operator) + out.WriteString(pe.Right.String()) + out.WriteString(")") + + return out.String() +} + +type InfixExpression struct { + Token token.Token // The operator token, e.g. + + Left Expression + Operator string + Right Expression +} + +func (oe *InfixExpression) expressionNode() {} +func (oe *InfixExpression) TokenLiteral() string { return oe.Token.Literal } +func (oe *InfixExpression) String() string { + var out bytes.Buffer + + out.WriteString("(") + out.WriteString(oe.Left.String()) + out.WriteString(" " + oe.Operator + " ") + out.WriteString(oe.Right.String()) + out.WriteString(")") + + return out.String() +} + +type IfExpression struct { + Token token.Token // The 'if' token + Condition Expression + Consequence *BlockStatement + Alternative *BlockStatement +} + +func (ie *IfExpression) expressionNode() {} +func (ie *IfExpression) TokenLiteral() string { return ie.Token.Literal } +func (ie *IfExpression) String() string { + var out bytes.Buffer + + out.WriteString("if") + out.WriteString(ie.Condition.String()) + out.WriteString(" ") + out.WriteString(ie.Consequence.String()) + + if ie.Alternative != nil { + out.WriteString("else ") + out.WriteString(ie.Alternative.String()) + } + + return out.String() +} + +type FunctionLiteral struct { + Token token.Token // The 'fn' token + Parameters []*Identifier + Body *BlockStatement + Name string +} + +func (fl *FunctionLiteral) expressionNode() {} +func (fl *FunctionLiteral) TokenLiteral() string { return fl.Token.Literal } +func (fl *FunctionLiteral) String() string { + var out bytes.Buffer + + params := []string{} + for _, p := range fl.Parameters { + params = append(params, p.String()) + } + + out.WriteString(fl.TokenLiteral()) + if fl.Name != "" { + out.WriteString(fmt.Sprintf("<%s>", fl.Name)) + } + out.WriteString("(") + out.WriteString(strings.Join(params, ", ")) + out.WriteString(") ") + out.WriteString(fl.Body.String()) + + return out.String() +} + +type CallExpression struct { + Token token.Token // The '(' token + Function Expression // Identifier or FunctionLiteral + Arguments []Expression +} + +func (ce *CallExpression) expressionNode() {} +func (ce *CallExpression) TokenLiteral() string { return ce.Token.Literal } +func (ce *CallExpression) String() string { + var out bytes.Buffer + + args := []string{} + for _, a := range ce.Arguments { + args = append(args, a.String()) + } + + out.WriteString(ce.Function.String()) + out.WriteString("(") + out.WriteString(strings.Join(args, ", ")) + out.WriteString(")") + + return out.String() +} + +type StringLiteral struct { + Token token.Token + Value string +} + +func (sl *StringLiteral) expressionNode() {} +func (sl *StringLiteral) TokenLiteral() string { return sl.Token.Literal } +func (sl *StringLiteral) String() string { return sl.Token.Literal } + +type ArrayLiteral struct { + Token token.Token // the '[' token + Elements []Expression +} + +func (al *ArrayLiteral) expressionNode() {} +func (al *ArrayLiteral) TokenLiteral() string { return al.Token.Literal } +func (al *ArrayLiteral) String() string { + var out bytes.Buffer + + elements := []string{} + for _, el := range al.Elements { + elements = append(elements, el.String()) + } + + out.WriteString("[") + out.WriteString(strings.Join(elements, ", ")) + out.WriteString("]") + + return out.String() +} + +type IndexExpression struct { + Token token.Token // The [ token + Left Expression + Index Expression +} + +func (ie *IndexExpression) expressionNode() {} +func (ie *IndexExpression) TokenLiteral() string { return ie.Token.Literal } +func (ie *IndexExpression) String() string { + var out bytes.Buffer + + out.WriteString("(") + out.WriteString(ie.Left.String()) + out.WriteString("[") + out.WriteString(ie.Index.String()) + out.WriteString("])") + + return out.String() +} + +type HashLiteral struct { + Token token.Token // the '{' token + Pairs map[Expression]Expression +} + +func (hl *HashLiteral) expressionNode() {} +func (hl *HashLiteral) TokenLiteral() string { return hl.Token.Literal } +func (hl *HashLiteral) String() string { + var out bytes.Buffer + + pairs := []string{} + for key, value := range hl.Pairs { + pairs = append(pairs, key.String()+":"+value.String()) + } + + out.WriteString("{") + out.WriteString(strings.Join(pairs, ", ")) + out.WriteString("}") + + return out.String() +} diff --git a/wcig_code_1_2/10/src/monkey/ast/ast_test.go b/wcig_code_1_2/10/src/monkey/ast/ast_test.go new file mode 100644 index 0000000..14a49dc --- /dev/null +++ b/wcig_code_1_2/10/src/monkey/ast/ast_test.go @@ -0,0 +1,28 @@ +package ast + +import ( + "monkey/token" + "testing" +) + +func TestString(t *testing.T) { + program := &Program{ + Statements: []Statement{ + &LetStatement{ + Token: token.Token{Type: token.LET, Literal: "let"}, + Name: &Identifier{ + Token: token.Token{Type: token.IDENT, Literal: "myVar"}, + Value: "myVar", + }, + Value: &Identifier{ + Token: token.Token{Type: token.IDENT, Literal: "anotherVar"}, + Value: "anotherVar", + }, + }, + }, + } + + if program.String() != "let myVar = anotherVar;" { + t.Errorf("program.String() wrong. got=%q", program.String()) + } +} diff --git a/wcig_code_1_2/10/src/monkey/benchmark/main.go b/wcig_code_1_2/10/src/monkey/benchmark/main.go new file mode 100644 index 0000000..86255a6 --- /dev/null +++ b/wcig_code_1_2/10/src/monkey/benchmark/main.go @@ -0,0 +1,75 @@ +package main + +import ( + "flag" + "fmt" + "time" + + "monkey/compiler" + "monkey/evaluator" + "monkey/lexer" + "monkey/object" + "monkey/parser" + "monkey/vm" +) + +var engine = flag.String("engine", "vm", "use 'vm' or 'eval'") + +var input = ` +let fibonacci = fn(x) { + if (x == 0) { + 0 + } else { + if (x == 1) { + return 1; + } else { + fibonacci(x - 1) + fibonacci(x - 2); + } + } +}; +fibonacci(35); +` + +func main() { + flag.Parse() + + var duration time.Duration + var result object.Object + + l := lexer.New(input) + p := parser.New(l) + program := p.ParseProgram() + + if *engine == "vm" { + comp := compiler.New() + err := comp.Compile(program) + if err != nil { + fmt.Printf("compiler error: %s", err) + return + } + + machine := vm.New(comp.Bytecode()) + + start := time.Now() + + err = machine.Run() + if err != nil { + fmt.Printf("vm error: %s", err) + return + } + + duration = time.Since(start) + result = machine.LastPoppedStackElem() + } else { + env := object.NewEnvironment() + start := time.Now() + result = evaluator.Eval(program, env) + duration = time.Since(start) + } + + fmt.Printf( + "engine=%s, result=%s, duration=%s\n", + *engine, + result.Inspect(), + duration) +} diff --git a/wcig_code_1_2/10/src/monkey/code/code.go b/wcig_code_1_2/10/src/monkey/code/code.go new file mode 100644 index 0000000..24d81b8 --- /dev/null +++ b/wcig_code_1_2/10/src/monkey/code/code.go @@ -0,0 +1,219 @@ +package code + +import ( + "bytes" + "encoding/binary" + "fmt" +) + +type Instructions []byte + +func (ins Instructions) String() string { + var out bytes.Buffer + + i := 0 + for i < len(ins) { + def, err := Lookup(ins[i]) + if err != nil { + fmt.Fprintf(&out, "ERROR: %s\n", err) + continue + } + + operands, read := ReadOperands(def, ins[i+1:]) + + fmt.Fprintf(&out, "%04d %s\n", i, ins.fmtInstruction(def, operands)) + + i += 1 + read + } + + return out.String() +} + +func (ins Instructions) fmtInstruction(def *Definition, operands []int) string { + operandCount := len(def.OperandWidths) + + if len(operands) != operandCount { + return fmt.Sprintf("ERROR: operand len %d does not match defined %d\n", + len(operands), operandCount) + } + + switch operandCount { + case 0: + return def.Name + case 1: + return fmt.Sprintf("%s %d", def.Name, operands[0]) + case 2: + return fmt.Sprintf("%s %d %d", def.Name, operands[0], operands[1]) + } + + return fmt.Sprintf("ERROR: unhandled operandCount for %s\n", def.Name) +} + +type Opcode byte + +const ( + OpConstant Opcode = iota + + OpAdd + + OpPop + + OpSub + OpMul + OpDiv + + OpTrue + OpFalse + + OpEqual + OpNotEqual + OpGreaterThan + + OpMinus + OpBang + + OpJumpNotTruthy + OpJump + + OpNull + + OpGetGlobal + OpSetGlobal + + OpArray + OpHash + OpIndex + + OpCall + + OpReturnValue + OpReturn + + OpGetLocal + OpSetLocal + + OpGetBuiltin + + OpClosure + + OpGetFree + + OpCurrentClosure +) + +type Definition struct { + Name string + OperandWidths []int +} + +var definitions = map[Opcode]*Definition{ + OpConstant: {"OpConstant", []int{2}}, + + OpAdd: {"OpAdd", []int{}}, + + OpPop: {"OpPop", []int{}}, + + OpSub: {"OpSub", []int{}}, + OpMul: {"OpMul", []int{}}, + OpDiv: {"OpDiv", []int{}}, + + OpTrue: {"OpTrue", []int{}}, + OpFalse: {"OpFalse", []int{}}, + + OpEqual: {"OpEqual", []int{}}, + OpNotEqual: {"OpNotEqual", []int{}}, + OpGreaterThan: {"OpGreaterThan", []int{}}, + + OpMinus: {"OpMinus", []int{}}, + OpBang: {"OpBang", []int{}}, + + OpJumpNotTruthy: {"OpJumpNotTruthy", []int{2}}, + OpJump: {"OpJump", []int{2}}, + + OpNull: {"OpNull", []int{}}, + + OpGetGlobal: {"OpGetGlobal", []int{2}}, + OpSetGlobal: {"OpSetGlobal", []int{2}}, + + OpArray: {"OpArray", []int{2}}, + OpHash: {"OpHash", []int{2}}, + OpIndex: {"OpIndex", []int{}}, + + OpCall: {"OpCall", []int{1}}, + + OpReturnValue: {"OpReturnValue", []int{}}, + OpReturn: {"OpReturn", []int{}}, + + OpGetLocal: {"OpGetLocal", []int{1}}, + OpSetLocal: {"OpSetLocal", []int{1}}, + + OpGetBuiltin: {"OpGetBuiltin", []int{1}}, + + OpClosure: {"OpClosure", []int{2, 1}}, + + OpGetFree: {"OpGetFree", []int{1}}, + + OpCurrentClosure: {"OpCurrentClosure", []int{}}, +} + +func Lookup(op byte) (*Definition, error) { + def, ok := definitions[Opcode(op)] + if !ok { + return nil, fmt.Errorf("opcode %d undefined", op) + } + + return def, nil +} + +func Make(op Opcode, operands ...int) []byte { + def, ok := definitions[op] + if !ok { + return []byte{} + } + + instructionLen := 1 + for _, w := range def.OperandWidths { + instructionLen += w + } + + instruction := make([]byte, instructionLen) + instruction[0] = byte(op) + + offset := 1 + for i, o := range operands { + width := def.OperandWidths[i] + switch width { + case 2: + binary.BigEndian.PutUint16(instruction[offset:], uint16(o)) + case 1: + instruction[offset] = byte(o) + } + offset += width + } + + return instruction +} + +func ReadOperands(def *Definition, ins Instructions) ([]int, int) { + operands := make([]int, len(def.OperandWidths)) + offset := 0 + + for i, width := range def.OperandWidths { + switch width { + case 2: + operands[i] = int(ReadUint16(ins[offset:])) + case 1: + operands[i] = int(ReadUint8(ins[offset:])) + } + + offset += width + } + + return operands, offset +} + +func ReadUint8(ins Instructions) uint8 { return uint8(ins[0]) } + +func ReadUint16(ins Instructions) uint16 { + return binary.BigEndian.Uint16(ins) +} diff --git a/wcig_code_1_2/10/src/monkey/code/code_test.go b/wcig_code_1_2/10/src/monkey/code/code_test.go new file mode 100644 index 0000000..060500c --- /dev/null +++ b/wcig_code_1_2/10/src/monkey/code/code_test.go @@ -0,0 +1,91 @@ +package code + +import "testing" + +func TestMake(t *testing.T) { + tests := []struct { + op Opcode + operands []int + expected []byte + }{ + {OpConstant, []int{65534}, []byte{byte(OpConstant), 255, 254}}, + {OpAdd, []int{}, []byte{byte(OpAdd)}}, + {OpGetLocal, []int{255}, []byte{byte(OpGetLocal), 255}}, + {OpClosure, []int{65534, 255}, []byte{byte(OpClosure), 255, 254, 255}}, + } + + for _, tt := range tests { + instruction := Make(tt.op, tt.operands...) + + if len(instruction) != len(tt.expected) { + t.Errorf("instruction has wrong length. want=%d, got=%d", + len(tt.expected), len(instruction)) + } + + for i, b := range tt.expected { + if instruction[i] != tt.expected[i] { + t.Errorf("wrong byte at pos %d. want=%d, got=%d", + i, b, instruction[i]) + } + } + } +} + +func TestInstructionsString(t *testing.T) { + instructions := []Instructions{ + Make(OpAdd), + Make(OpGetLocal, 1), + Make(OpConstant, 2), + Make(OpConstant, 65535), + Make(OpClosure, 65535, 255), + } + + expected := `0000 OpAdd +0001 OpGetLocal 1 +0003 OpConstant 2 +0006 OpConstant 65535 +0009 OpClosure 65535 255 +` + + concatted := Instructions{} + for _, ins := range instructions { + concatted = append(concatted, ins...) + } + + if concatted.String() != expected { + t.Errorf("instructions wrongly formatted.\nwant=%q\ngot=%q", + expected, concatted.String()) + } +} + +func TestReadOperands(t *testing.T) { + tests := []struct { + op Opcode + operands []int + bytesRead int + }{ + {OpConstant, []int{65535}, 2}, + {OpGetLocal, []int{255}, 1}, + {OpClosure, []int{65535, 255}, 3}, + } + + for _, tt := range tests { + instruction := Make(tt.op, tt.operands...) + + def, err := Lookup(byte(tt.op)) + if err != nil { + t.Fatalf("definition not found: %q\n", err) + } + + operandsRead, n := ReadOperands(def, instruction[1:]) + if n != tt.bytesRead { + t.Fatalf("n wrong. want=%d, got=%d", tt.bytesRead, n) + } + + for i, want := range tt.operands { + if operandsRead[i] != want { + t.Errorf("operand wrong. want=%d, got=%d", want, operandsRead[i]) + } + } + } +} diff --git a/wcig_code_1_2/10/src/monkey/compiler/compiler.go b/wcig_code_1_2/10/src/monkey/compiler/compiler.go new file mode 100644 index 0000000..ff9a840 --- /dev/null +++ b/wcig_code_1_2/10/src/monkey/compiler/compiler.go @@ -0,0 +1,456 @@ +package compiler + +import ( + "fmt" + "monkey/ast" + "monkey/code" + "monkey/object" + "sort" +) + +type Compiler struct { + constants []object.Object + + symbolTable *SymbolTable + + scopes []CompilationScope + scopeIndex int +} + +func New() *Compiler { + mainScope := CompilationScope{ + instructions: code.Instructions{}, + lastInstruction: EmittedInstruction{}, + previousInstruction: EmittedInstruction{}, + } + + symbolTable := NewSymbolTable() + + for i, v := range object.Builtins { + symbolTable.DefineBuiltin(i, v.Name) + } + + return &Compiler{ + constants: []object.Object{}, + symbolTable: symbolTable, + scopes: []CompilationScope{mainScope}, + scopeIndex: 0, + } +} + +func NewWithState(s *SymbolTable, constants []object.Object) *Compiler { + compiler := New() + compiler.symbolTable = s + compiler.constants = constants + return compiler +} + +func (c *Compiler) Compile(node ast.Node) error { + switch node := node.(type) { + case *ast.Program: + for _, s := range node.Statements { + err := c.Compile(s) + if err != nil { + return err + } + } + + case *ast.ExpressionStatement: + err := c.Compile(node.Expression) + if err != nil { + return err + } + c.emit(code.OpPop) + + case *ast.InfixExpression: + if node.Operator == "<" { + err := c.Compile(node.Right) + if err != nil { + return err + } + + err = c.Compile(node.Left) + if err != nil { + return err + } + c.emit(code.OpGreaterThan) + return nil + } + + err := c.Compile(node.Left) + if err != nil { + return err + } + + err = c.Compile(node.Right) + if err != nil { + return err + } + + switch node.Operator { + case "+": + c.emit(code.OpAdd) + case "-": + c.emit(code.OpSub) + case "*": + c.emit(code.OpMul) + case "/": + c.emit(code.OpDiv) + case ">": + c.emit(code.OpGreaterThan) + case "==": + c.emit(code.OpEqual) + case "!=": + c.emit(code.OpNotEqual) + default: + return fmt.Errorf("unknown operator %s", node.Operator) + } + + case *ast.IntegerLiteral: + integer := &object.Integer{Value: node.Value} + c.emit(code.OpConstant, c.addConstant(integer)) + + case *ast.Boolean: + if node.Value { + c.emit(code.OpTrue) + } else { + c.emit(code.OpFalse) + } + + case *ast.PrefixExpression: + err := c.Compile(node.Right) + if err != nil { + return err + } + + switch node.Operator { + case "!": + c.emit(code.OpBang) + case "-": + c.emit(code.OpMinus) + default: + return fmt.Errorf("unknown operator %s", node.Operator) + } + + case *ast.IfExpression: + err := c.Compile(node.Condition) + if err != nil { + return err + } + + // Emit an `OpJumpNotTruthy` with a bogus value + jumpNotTruthyPos := c.emit(code.OpJumpNotTruthy, 9999) + + err = c.Compile(node.Consequence) + if err != nil { + return err + } + + if c.lastInstructionIs(code.OpPop) { + c.removeLastPop() + } + + // Emit an `OpJump` with a bogus value + jumpPos := c.emit(code.OpJump, 9999) + + afterConsequencePos := len(c.currentInstructions()) + c.changeOperand(jumpNotTruthyPos, afterConsequencePos) + + if node.Alternative == nil { + c.emit(code.OpNull) + } else { + err := c.Compile(node.Alternative) + if err != nil { + return err + } + + if c.lastInstructionIs(code.OpPop) { + c.removeLastPop() + } + } + + afterAlternativePos := len(c.currentInstructions()) + c.changeOperand(jumpPos, afterAlternativePos) + + case *ast.BlockStatement: + for _, s := range node.Statements { + err := c.Compile(s) + if err != nil { + return err + } + } + + case *ast.LetStatement: + symbol := c.symbolTable.Define(node.Name.Value) + err := c.Compile(node.Value) + if err != nil { + return err + } + + if symbol.Scope == GlobalScope { + c.emit(code.OpSetGlobal, symbol.Index) + } else { + c.emit(code.OpSetLocal, symbol.Index) + } + + case *ast.Identifier: + symbol, ok := c.symbolTable.Resolve(node.Value) + if !ok { + return fmt.Errorf("undefined variable %s", node.Value) + } + + c.loadSymbol(symbol) + + case *ast.StringLiteral: + str := &object.String{Value: node.Value} + c.emit(code.OpConstant, c.addConstant(str)) + + case *ast.ArrayLiteral: + for _, el := range node.Elements { + err := c.Compile(el) + if err != nil { + return err + } + } + + c.emit(code.OpArray, len(node.Elements)) + + case *ast.HashLiteral: + keys := []ast.Expression{} + for k := range node.Pairs { + keys = append(keys, k) + } + sort.Slice(keys, func(i, j int) bool { + return keys[i].String() < keys[j].String() + }) + + for _, k := range keys { + err := c.Compile(k) + if err != nil { + return err + } + err = c.Compile(node.Pairs[k]) + if err != nil { + return err + } + } + + c.emit(code.OpHash, len(node.Pairs)*2) + + case *ast.IndexExpression: + err := c.Compile(node.Left) + if err != nil { + return err + } + + err = c.Compile(node.Index) + if err != nil { + return err + } + + c.emit(code.OpIndex) + + case *ast.FunctionLiteral: + c.enterScope() + + if node.Name != "" { + c.symbolTable.DefineFunctionName(node.Name) + } + + for _, p := range node.Parameters { + c.symbolTable.Define(p.Value) + } + + err := c.Compile(node.Body) + if err != nil { + return err + } + + if c.lastInstructionIs(code.OpPop) { + c.replaceLastPopWithReturn() + } + if !c.lastInstructionIs(code.OpReturnValue) { + c.emit(code.OpReturn) + } + + freeSymbols := c.symbolTable.FreeSymbols + numLocals := c.symbolTable.numDefinitions + instructions := c.leaveScope() + + for _, s := range freeSymbols { + c.loadSymbol(s) + } + + compiledFn := &object.CompiledFunction{ + Instructions: instructions, + NumLocals: numLocals, + NumParameters: len(node.Parameters), + } + + fnIndex := c.addConstant(compiledFn) + c.emit(code.OpClosure, fnIndex, len(freeSymbols)) + + case *ast.ReturnStatement: + err := c.Compile(node.ReturnValue) + if err != nil { + return err + } + + c.emit(code.OpReturnValue) + + case *ast.CallExpression: + err := c.Compile(node.Function) + if err != nil { + return err + } + + for _, a := range node.Arguments { + err := c.Compile(a) + if err != nil { + return err + } + } + + c.emit(code.OpCall, len(node.Arguments)) + + } + + return nil +} + +func (c *Compiler) Bytecode() *Bytecode { + return &Bytecode{ + Instructions: c.currentInstructions(), + Constants: c.constants, + } +} + +func (c *Compiler) addConstant(obj object.Object) int { + c.constants = append(c.constants, obj) + return len(c.constants) - 1 +} + +func (c *Compiler) emit(op code.Opcode, operands ...int) int { + ins := code.Make(op, operands...) + pos := c.addInstruction(ins) + + c.setLastInstruction(op, pos) + + return pos +} + +func (c *Compiler) addInstruction(ins []byte) int { + posNewInstruction := len(c.currentInstructions()) + updatedInstructions := append(c.currentInstructions(), ins...) + + c.scopes[c.scopeIndex].instructions = updatedInstructions + + return posNewInstruction +} + +func (c *Compiler) setLastInstruction(op code.Opcode, pos int) { + previous := c.scopes[c.scopeIndex].lastInstruction + last := EmittedInstruction{Opcode: op, Position: pos} + + c.scopes[c.scopeIndex].previousInstruction = previous + c.scopes[c.scopeIndex].lastInstruction = last +} + +func (c *Compiler) lastInstructionIs(op code.Opcode) bool { + if len(c.currentInstructions()) == 0 { + return false + } + + return c.scopes[c.scopeIndex].lastInstruction.Opcode == op +} + +func (c *Compiler) removeLastPop() { + last := c.scopes[c.scopeIndex].lastInstruction + previous := c.scopes[c.scopeIndex].previousInstruction + + old := c.currentInstructions() + new := old[:last.Position] + + c.scopes[c.scopeIndex].instructions = new + c.scopes[c.scopeIndex].lastInstruction = previous +} + +func (c *Compiler) replaceInstruction(pos int, newInstruction []byte) { + ins := c.currentInstructions() + + for i := 0; i < len(newInstruction); i++ { + ins[pos+i] = newInstruction[i] + } +} + +func (c *Compiler) changeOperand(opPos int, operand int) { + op := code.Opcode(c.currentInstructions()[opPos]) + newInstruction := code.Make(op, operand) + + c.replaceInstruction(opPos, newInstruction) +} + +func (c *Compiler) currentInstructions() code.Instructions { + return c.scopes[c.scopeIndex].instructions +} + +func (c *Compiler) enterScope() { + scope := CompilationScope{ + instructions: code.Instructions{}, + lastInstruction: EmittedInstruction{}, + previousInstruction: EmittedInstruction{}, + } + c.scopes = append(c.scopes, scope) + c.scopeIndex++ + + c.symbolTable = NewEnclosedSymbolTable(c.symbolTable) +} + +func (c *Compiler) leaveScope() code.Instructions { + instructions := c.currentInstructions() + + c.scopes = c.scopes[:len(c.scopes)-1] + c.scopeIndex-- + + c.symbolTable = c.symbolTable.Outer + + return instructions +} + +func (c *Compiler) replaceLastPopWithReturn() { + lastPos := c.scopes[c.scopeIndex].lastInstruction.Position + c.replaceInstruction(lastPos, code.Make(code.OpReturnValue)) + + c.scopes[c.scopeIndex].lastInstruction.Opcode = code.OpReturnValue +} + +func (c *Compiler) loadSymbol(s Symbol) { + switch s.Scope { + case GlobalScope: + c.emit(code.OpGetGlobal, s.Index) + case LocalScope: + c.emit(code.OpGetLocal, s.Index) + case BuiltinScope: + c.emit(code.OpGetBuiltin, s.Index) + case FreeScope: + c.emit(code.OpGetFree, s.Index) + case FunctionScope: + c.emit(code.OpCurrentClosure) + } +} + +type Bytecode struct { + Instructions code.Instructions + Constants []object.Object +} + +type EmittedInstruction struct { + Opcode code.Opcode + Position int +} + +type CompilationScope struct { + instructions code.Instructions + lastInstruction EmittedInstruction + previousInstruction EmittedInstruction +} diff --git a/wcig_code_1_2/10/src/monkey/compiler/compiler_test.go b/wcig_code_1_2/10/src/monkey/compiler/compiler_test.go new file mode 100644 index 0000000..7f3e248 --- /dev/null +++ b/wcig_code_1_2/10/src/monkey/compiler/compiler_test.go @@ -0,0 +1,1106 @@ +package compiler + +import ( + "fmt" + "monkey/ast" + "monkey/code" + "monkey/lexer" + "monkey/object" + "monkey/parser" + "testing" +) + +func TestCompilerScopes(t *testing.T) { + compiler := New() + if compiler.scopeIndex != 0 { + t.Errorf("scopeIndex wrong. got=%d, want=%d", compiler.scopeIndex, 0) + } + globalSymbolTable := compiler.symbolTable + + compiler.emit(code.OpMul) + + compiler.enterScope() + if compiler.scopeIndex != 1 { + t.Errorf("scopeIndex wrong. got=%d, want=%d", compiler.scopeIndex, 1) + } + + compiler.emit(code.OpSub) + + if len(compiler.scopes[compiler.scopeIndex].instructions) != 1 { + t.Errorf("instructions length wrong. got=%d", + len(compiler.scopes[compiler.scopeIndex].instructions)) + } + + last := compiler.scopes[compiler.scopeIndex].lastInstruction + if last.Opcode != code.OpSub { + t.Errorf("lastInstruction.Opcode wrong. got=%d, want=%d", + last.Opcode, code.OpSub) + } + + if compiler.symbolTable.Outer != globalSymbolTable { + t.Errorf("compiler did not enclose symbolTable") + } + + compiler.leaveScope() + if compiler.scopeIndex != 0 { + t.Errorf("scopeIndex wrong. got=%d, want=%d", + compiler.scopeIndex, 0) + } + + if compiler.symbolTable != globalSymbolTable { + t.Errorf("compiler did not restore global symbol table") + } + if compiler.symbolTable.Outer != nil { + t.Errorf("compiler modified global symbol table incorrectly") + } + + compiler.emit(code.OpAdd) + + if len(compiler.scopes[compiler.scopeIndex].instructions) != 2 { + t.Errorf("instructions length wrong. got=%d", + len(compiler.scopes[compiler.scopeIndex].instructions)) + } + + last = compiler.scopes[compiler.scopeIndex].lastInstruction + if last.Opcode != code.OpAdd { + t.Errorf("lastInstruction.Opcode wrong. got=%d, want=%d", + last.Opcode, code.OpAdd) + } + + previous := compiler.scopes[compiler.scopeIndex].previousInstruction + if previous.Opcode != code.OpMul { + t.Errorf("previousInstruction.Opcode wrong. got=%d, want=%d", + previous.Opcode, code.OpMul) + } +} + +func TestIntegerArithmetic(t *testing.T) { + tests := []compilerTestCase{ + { + input: "1 + 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpAdd), + code.Make(code.OpPop), + }, + }, + { + input: "1; 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpPop), + code.Make(code.OpConstant, 1), + code.Make(code.OpPop), + }, + }, + { + input: "1 - 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpSub), + code.Make(code.OpPop), + }, + }, + { + input: "1 * 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpMul), + code.Make(code.OpPop), + }, + }, + { + input: "2 / 1", + expectedConstants: []interface{}{2, 1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpDiv), + code.Make(code.OpPop), + }, + }, + { + input: "-1", + expectedConstants: []interface{}{1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpMinus), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestBooleanExpressions(t *testing.T) { + tests := []compilerTestCase{ + { + input: "true", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpTrue), + code.Make(code.OpPop), + }, + }, + { + input: "false", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpFalse), + code.Make(code.OpPop), + }, + }, + { + input: "1 > 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpGreaterThan), + code.Make(code.OpPop), + }, + }, + { + input: "1 < 2", + expectedConstants: []interface{}{2, 1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpGreaterThan), + code.Make(code.OpPop), + }, + }, + { + input: "1 == 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpEqual), + code.Make(code.OpPop), + }, + }, + { + input: "1 != 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpNotEqual), + code.Make(code.OpPop), + }, + }, + { + input: "true == false", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpTrue), + code.Make(code.OpFalse), + code.Make(code.OpEqual), + code.Make(code.OpPop), + }, + }, + { + input: "true != false", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpTrue), + code.Make(code.OpFalse), + code.Make(code.OpNotEqual), + code.Make(code.OpPop), + }, + }, + { + input: "!true", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpTrue), + code.Make(code.OpBang), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestConditionals(t *testing.T) { + tests := []compilerTestCase{ + { + input: ` + if (true) { 10 }; 3333; + `, + expectedConstants: []interface{}{10, 3333}, + expectedInstructions: []code.Instructions{ + // 0000 + code.Make(code.OpTrue), + // 0001 + code.Make(code.OpJumpNotTruthy, 10), + // 0004 + code.Make(code.OpConstant, 0), + // 0007 + code.Make(code.OpJump, 11), + // 0010 + code.Make(code.OpNull), + // 0011 + code.Make(code.OpPop), + // 0012 + code.Make(code.OpConstant, 1), + // 0015 + code.Make(code.OpPop), + }, + }, + { + input: ` + if (true) { 10 } else { 20 }; 3333; + `, + expectedConstants: []interface{}{10, 20, 3333}, + expectedInstructions: []code.Instructions{ + // 0000 + code.Make(code.OpTrue), + // 0001 + code.Make(code.OpJumpNotTruthy, 10), + // 0004 + code.Make(code.OpConstant, 0), + // 0007 + code.Make(code.OpJump, 13), + // 0010 + code.Make(code.OpConstant, 1), + // 0013 + code.Make(code.OpPop), + // 0014 + code.Make(code.OpConstant, 2), + // 0017 + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestGlobalLetStatements(t *testing.T) { + tests := []compilerTestCase{ + { + input: ` + let one = 1; + let two = 2; + `, + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpSetGlobal, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpSetGlobal, 1), + }, + }, + { + input: ` + let one = 1; + one; + `, + expectedConstants: []interface{}{1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpSetGlobal, 0), + code.Make(code.OpGetGlobal, 0), + code.Make(code.OpPop), + }, + }, + { + input: ` + let one = 1; + let two = one; + two; + `, + expectedConstants: []interface{}{1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpSetGlobal, 0), + code.Make(code.OpGetGlobal, 0), + code.Make(code.OpSetGlobal, 1), + code.Make(code.OpGetGlobal, 1), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestFunctions(t *testing.T) { + tests := []compilerTestCase{ + { + input: `fn() { return 5 + 10 }`, + expectedConstants: []interface{}{ + 5, + 10, + []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpAdd), + code.Make(code.OpReturnValue), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpClosure, 2, 0), + code.Make(code.OpPop), + }, + }, + { + input: `fn() { 5 + 10 }`, + expectedConstants: []interface{}{ + 5, + 10, + []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpAdd), + code.Make(code.OpReturnValue), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpClosure, 2, 0), + code.Make(code.OpPop), + }, + }, + { + input: `fn() { 1; 2 }`, + expectedConstants: []interface{}{ + 1, + 2, + []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpPop), + code.Make(code.OpConstant, 1), + code.Make(code.OpReturnValue), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpClosure, 2, 0), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +type compilerTestCase struct { + input string + expectedConstants []interface{} + expectedInstructions []code.Instructions +} + +func runCompilerTests(t *testing.T, tests []compilerTestCase) { + t.Helper() + + for _, tt := range tests { + program := parse(tt.input) + + compiler := New() + err := compiler.Compile(program) + if err != nil { + t.Fatalf("compiler error: %s", err) + } + + bytecode := compiler.Bytecode() + + err = testInstructions(tt.expectedInstructions, bytecode.Instructions) + if err != nil { + t.Fatalf("testInstructions failed: %s", err) + } + + err = testConstants(t, tt.expectedConstants, bytecode.Constants) + if err != nil { + t.Fatalf("testConstants failed: %s", err) + } + } +} + +func TestStringExpressions(t *testing.T) { + tests := []compilerTestCase{ + { + input: `"monkey"`, + expectedConstants: []interface{}{"monkey"}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpPop), + }, + }, + { + input: `"mon" + "key"`, + expectedConstants: []interface{}{"mon", "key"}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpAdd), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestArrayLiterals(t *testing.T) { + tests := []compilerTestCase{ + { + input: "[]", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpArray, 0), + code.Make(code.OpPop), + }, + }, + { + input: "[1, 2, 3]", + expectedConstants: []interface{}{1, 2, 3}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpConstant, 2), + code.Make(code.OpArray, 3), + code.Make(code.OpPop), + }, + }, + { + input: "[1 + 2, 3 - 4, 5 * 6]", + expectedConstants: []interface{}{1, 2, 3, 4, 5, 6}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpAdd), + code.Make(code.OpConstant, 2), + code.Make(code.OpConstant, 3), + code.Make(code.OpSub), + code.Make(code.OpConstant, 4), + code.Make(code.OpConstant, 5), + code.Make(code.OpMul), + code.Make(code.OpArray, 3), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestHashLiterals(t *testing.T) { + tests := []compilerTestCase{ + { + input: "{}", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpHash, 0), + code.Make(code.OpPop), + }, + }, + { + input: "{1: 2, 3: 4, 5: 6}", + expectedConstants: []interface{}{1, 2, 3, 4, 5, 6}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpConstant, 2), + code.Make(code.OpConstant, 3), + code.Make(code.OpConstant, 4), + code.Make(code.OpConstant, 5), + code.Make(code.OpHash, 6), + code.Make(code.OpPop), + }, + }, + { + input: "{1: 2 + 3, 4: 5 * 6}", + expectedConstants: []interface{}{1, 2, 3, 4, 5, 6}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpConstant, 2), + code.Make(code.OpAdd), + code.Make(code.OpConstant, 3), + code.Make(code.OpConstant, 4), + code.Make(code.OpConstant, 5), + code.Make(code.OpMul), + code.Make(code.OpHash, 4), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestIndexExpressions(t *testing.T) { + tests := []compilerTestCase{ + { + input: "[1, 2, 3][1 + 1]", + expectedConstants: []interface{}{1, 2, 3, 1, 1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpConstant, 2), + code.Make(code.OpArray, 3), + code.Make(code.OpConstant, 3), + code.Make(code.OpConstant, 4), + code.Make(code.OpAdd), + code.Make(code.OpIndex), + code.Make(code.OpPop), + }, + }, + { + input: "{1: 2}[2 - 1]", + expectedConstants: []interface{}{1, 2, 2, 1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpHash, 2), + code.Make(code.OpConstant, 2), + code.Make(code.OpConstant, 3), + code.Make(code.OpSub), + code.Make(code.OpIndex), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestFunctionsWithoutReturnValue(t *testing.T) { + tests := []compilerTestCase{ + { + input: `fn() { }`, + expectedConstants: []interface{}{ + []code.Instructions{ + code.Make(code.OpReturn), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpClosure, 0, 0), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestFunctionCalls(t *testing.T) { + tests := []compilerTestCase{ + { + input: `fn() { 24 }();`, + expectedConstants: []interface{}{ + 24, + []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpReturnValue), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpClosure, 1, 0), + code.Make(code.OpCall, 0), + code.Make(code.OpPop), + }, + }, + { + input: ` + let noArg = fn() { 24 }; + noArg(); + `, + expectedConstants: []interface{}{ + 24, + []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpReturnValue), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpClosure, 1, 0), + code.Make(code.OpSetGlobal, 0), + code.Make(code.OpGetGlobal, 0), + code.Make(code.OpCall, 0), + code.Make(code.OpPop), + }, + }, + { + input: ` + let oneArg = fn(a) { a }; + oneArg(24); + `, + expectedConstants: []interface{}{ + []code.Instructions{ + code.Make(code.OpGetLocal, 0), + code.Make(code.OpReturnValue), + }, + 24, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpClosure, 0, 0), + code.Make(code.OpSetGlobal, 0), + code.Make(code.OpGetGlobal, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpCall, 1), + code.Make(code.OpPop), + }, + }, + { + input: ` + let manyArg = fn(a, b, c) { a; b; c }; + manyArg(24, 25, 26); + `, + expectedConstants: []interface{}{ + []code.Instructions{ + code.Make(code.OpGetLocal, 0), + code.Make(code.OpPop), + code.Make(code.OpGetLocal, 1), + code.Make(code.OpPop), + code.Make(code.OpGetLocal, 2), + code.Make(code.OpReturnValue), + }, + 24, + 25, + 26, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpClosure, 0, 0), + code.Make(code.OpSetGlobal, 0), + code.Make(code.OpGetGlobal, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpConstant, 2), + code.Make(code.OpConstant, 3), + code.Make(code.OpCall, 3), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestLetStatementScopes(t *testing.T) { + tests := []compilerTestCase{ + { + input: ` + let num = 55; + fn() { num } + `, + expectedConstants: []interface{}{ + 55, + []code.Instructions{ + code.Make(code.OpGetGlobal, 0), + code.Make(code.OpReturnValue), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpSetGlobal, 0), + code.Make(code.OpClosure, 1, 0), + code.Make(code.OpPop), + }, + }, + { + input: ` + fn() { + let num = 55; + num + } + `, + expectedConstants: []interface{}{ + 55, + []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpSetLocal, 0), + code.Make(code.OpGetLocal, 0), + code.Make(code.OpReturnValue), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpClosure, 1, 0), + code.Make(code.OpPop), + }, + }, + { + input: ` + fn() { + let a = 55; + let b = 77; + a + b + } + `, + expectedConstants: []interface{}{ + 55, + 77, + []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpSetLocal, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpSetLocal, 1), + code.Make(code.OpGetLocal, 0), + code.Make(code.OpGetLocal, 1), + code.Make(code.OpAdd), + code.Make(code.OpReturnValue), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpClosure, 2, 0), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestBuiltins(t *testing.T) { + tests := []compilerTestCase{ + { + input: ` + len([]); + push([], 1); + `, + expectedConstants: []interface{}{1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpGetBuiltin, 0), + code.Make(code.OpArray, 0), + code.Make(code.OpCall, 1), + code.Make(code.OpPop), + code.Make(code.OpGetBuiltin, 5), + code.Make(code.OpArray, 0), + code.Make(code.OpConstant, 0), + code.Make(code.OpCall, 2), + code.Make(code.OpPop), + }, + }, + { + input: `fn() { len([]) }`, + expectedConstants: []interface{}{ + []code.Instructions{ + code.Make(code.OpGetBuiltin, 0), + code.Make(code.OpArray, 0), + code.Make(code.OpCall, 1), + code.Make(code.OpReturnValue), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpClosure, 0, 0), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestClosures(t *testing.T) { + tests := []compilerTestCase{ + { + input: ` + fn(a) { + fn(b) { + a + b + } + } + `, + expectedConstants: []interface{}{ + []code.Instructions{ + code.Make(code.OpGetFree, 0), + code.Make(code.OpGetLocal, 0), + code.Make(code.OpAdd), + code.Make(code.OpReturnValue), + }, + []code.Instructions{ + code.Make(code.OpGetLocal, 0), + code.Make(code.OpClosure, 0, 1), + code.Make(code.OpReturnValue), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpClosure, 1, 0), + code.Make(code.OpPop), + }, + }, + { + input: ` + fn(a) { + fn(b) { + fn(c) { + a + b + c + } + } + }; + `, + expectedConstants: []interface{}{ + []code.Instructions{ + code.Make(code.OpGetFree, 0), + code.Make(code.OpGetFree, 1), + code.Make(code.OpAdd), + code.Make(code.OpGetLocal, 0), + code.Make(code.OpAdd), + code.Make(code.OpReturnValue), + }, + []code.Instructions{ + code.Make(code.OpGetFree, 0), + code.Make(code.OpGetLocal, 0), + code.Make(code.OpClosure, 0, 2), + code.Make(code.OpReturnValue), + }, + []code.Instructions{ + code.Make(code.OpGetLocal, 0), + code.Make(code.OpClosure, 1, 1), + code.Make(code.OpReturnValue), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpClosure, 2, 0), + code.Make(code.OpPop), + }, + }, + { + input: ` + let global = 55; + + fn() { + let a = 66; + + fn() { + let b = 77; + + fn() { + let c = 88; + + global + a + b + c; + } + } + } + `, + expectedConstants: []interface{}{ + 55, + 66, + 77, + 88, + []code.Instructions{ + code.Make(code.OpConstant, 3), + code.Make(code.OpSetLocal, 0), + code.Make(code.OpGetGlobal, 0), + code.Make(code.OpGetFree, 0), + code.Make(code.OpAdd), + code.Make(code.OpGetFree, 1), + code.Make(code.OpAdd), + code.Make(code.OpGetLocal, 0), + code.Make(code.OpAdd), + code.Make(code.OpReturnValue), + }, + []code.Instructions{ + code.Make(code.OpConstant, 2), + code.Make(code.OpSetLocal, 0), + code.Make(code.OpGetFree, 0), + code.Make(code.OpGetLocal, 0), + code.Make(code.OpClosure, 4, 2), + code.Make(code.OpReturnValue), + }, + []code.Instructions{ + code.Make(code.OpConstant, 1), + code.Make(code.OpSetLocal, 0), + code.Make(code.OpGetLocal, 0), + code.Make(code.OpClosure, 5, 1), + code.Make(code.OpReturnValue), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpSetGlobal, 0), + code.Make(code.OpClosure, 6, 0), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func TestRecursiveFunctions(t *testing.T) { + tests := []compilerTestCase{ + { + input: ` + let countDown = fn(x) { countDown(x - 1); }; + countDown(1); + `, + expectedConstants: []interface{}{ + 1, + []code.Instructions{ + code.Make(code.OpCurrentClosure), + code.Make(code.OpGetLocal, 0), + code.Make(code.OpConstant, 0), + code.Make(code.OpSub), + code.Make(code.OpCall, 1), + code.Make(code.OpReturnValue), + }, + 1, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpClosure, 1, 0), + code.Make(code.OpSetGlobal, 0), + code.Make(code.OpGetGlobal, 0), + code.Make(code.OpConstant, 2), + code.Make(code.OpCall, 1), + code.Make(code.OpPop), + }, + }, + { + input: ` + let wrapper = fn() { + let countDown = fn(x) { countDown(x - 1); }; + countDown(1); + }; + wrapper(); + `, + expectedConstants: []interface{}{ + 1, + []code.Instructions{ + code.Make(code.OpCurrentClosure), + code.Make(code.OpGetLocal, 0), + code.Make(code.OpConstant, 0), + code.Make(code.OpSub), + code.Make(code.OpCall, 1), + code.Make(code.OpReturnValue), + }, + 1, + []code.Instructions{ + code.Make(code.OpClosure, 1, 0), + code.Make(code.OpSetLocal, 0), + code.Make(code.OpGetLocal, 0), + code.Make(code.OpConstant, 2), + code.Make(code.OpCall, 1), + code.Make(code.OpReturnValue), + }, + }, + expectedInstructions: []code.Instructions{ + code.Make(code.OpClosure, 3, 0), + code.Make(code.OpSetGlobal, 0), + code.Make(code.OpGetGlobal, 0), + code.Make(code.OpCall, 0), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + +func parse(input string) *ast.Program { + l := lexer.New(input) + p := parser.New(l) + return p.ParseProgram() +} + +func testInstructions( + expected []code.Instructions, + actual code.Instructions, +) error { + concatted := concatInstructions(expected) + + if len(actual) != len(concatted) { + return fmt.Errorf("wrong instructions length.\nwant=%q\ngot =%q", + concatted, actual) + } + + for i, ins := range concatted { + if actual[i] != ins { + return fmt.Errorf("wrong instruction at %d.\nwant=%q\ngot =%q", + i, concatted, actual) + } + } + + return nil +} + +func concatInstructions(s []code.Instructions) code.Instructions { + out := code.Instructions{} + + for _, ins := range s { + out = append(out, ins...) + } + + return out +} + +func testConstants( + t *testing.T, + expected []interface{}, + actual []object.Object, +) error { + if len(expected) != len(actual) { + return fmt.Errorf("wrong number of constants. got=%d, want=%d", + len(actual), len(expected)) + } + + for i, constant := range expected { + switch constant := constant.(type) { + case string: + err := testStringObject(constant, actual[i]) + if err != nil { + return fmt.Errorf("constant %d - testStringObject failed: %s", + i, err) + } + case int: + err := testIntegerObject(int64(constant), actual[i]) + if err != nil { + return fmt.Errorf("constant %d - testIntegerObject failed: %s", + i, err) + } + case []code.Instructions: + fn, ok := actual[i].(*object.CompiledFunction) + if !ok { + return fmt.Errorf("constant %d - not a function: %T", + i, actual[i]) + } + + err := testInstructions(constant, fn.Instructions) + if err != nil { + return fmt.Errorf("constant %d - testInstructions failed: %s", + i, err) + } + } + } + + return nil +} + +func testIntegerObject(expected int64, actual object.Object) error { + result, ok := actual.(*object.Integer) + if !ok { + return fmt.Errorf("object is not Integer. got=%T (%+v)", + actual, actual) + } + + if result.Value != expected { + return fmt.Errorf("object has wrong value. got=%d, want=%d", + result.Value, expected) + } + + return nil +} + +func testStringObject(expected string, actual object.Object) error { + result, ok := actual.(*object.String) + if !ok { + return fmt.Errorf("object is not String. got=%T (%+v)", + actual, actual) + } + + if result.Value != expected { + return fmt.Errorf("object has wrong value. got=%q, want=%q", + result.Value, expected) + } + + return nil +} diff --git a/wcig_code_1_2/10/src/monkey/compiler/symbol_table.go b/wcig_code_1_2/10/src/monkey/compiler/symbol_table.go new file mode 100644 index 0000000..10f4d03 --- /dev/null +++ b/wcig_code_1_2/10/src/monkey/compiler/symbol_table.go @@ -0,0 +1,91 @@ +package compiler + +type SymbolScope string + +const ( + LocalScope SymbolScope = "LOCAL" + GlobalScope SymbolScope = "GLOBAL" + BuiltinScope SymbolScope = "BUILTIN" + FreeScope SymbolScope = "FREE" + FunctionScope SymbolScope = "FUNCTION" +) + +type Symbol struct { + Name string + Scope SymbolScope + Index int +} + +type SymbolTable struct { + Outer *SymbolTable + + store map[string]Symbol + numDefinitions int + + FreeSymbols []Symbol +} + +func NewEnclosedSymbolTable(outer *SymbolTable) *SymbolTable { + s := NewSymbolTable() + s.Outer = outer + return s +} + +func NewSymbolTable() *SymbolTable { + s := make(map[string]Symbol) + free := []Symbol{} + return &SymbolTable{store: s, FreeSymbols: free} +} + +func (s *SymbolTable) Define(name string) Symbol { + symbol := Symbol{Name: name, Index: s.numDefinitions} + if s.Outer == nil { + symbol.Scope = GlobalScope + } else { + symbol.Scope = LocalScope + } + + s.store[name] = symbol + s.numDefinitions++ + return symbol +} + +func (s *SymbolTable) Resolve(name string) (Symbol, bool) { + obj, ok := s.store[name] + if !ok && s.Outer != nil { + obj, ok = s.Outer.Resolve(name) + if !ok { + return obj, ok + } + + if obj.Scope == GlobalScope || obj.Scope == BuiltinScope { + return obj, ok + } + + free := s.defineFree(obj) + return free, true + } + return obj, ok +} + +func (s *SymbolTable) DefineBuiltin(index int, name string) Symbol { + symbol := Symbol{Name: name, Index: index, Scope: BuiltinScope} + s.store[name] = symbol + return symbol +} + +func (s *SymbolTable) DefineFunctionName(name string) Symbol { + symbol := Symbol{Name: name, Index: 0, Scope: FunctionScope} + s.store[name] = symbol + return symbol +} + +func (s *SymbolTable) defineFree(original Symbol) Symbol { + s.FreeSymbols = append(s.FreeSymbols, original) + + symbol := Symbol{Name: original.Name, Index: len(s.FreeSymbols) - 1} + symbol.Scope = FreeScope + + s.store[original.Name] = symbol + return symbol +} diff --git a/wcig_code_1_2/10/src/monkey/compiler/symbol_table_test.go b/wcig_code_1_2/10/src/monkey/compiler/symbol_table_test.go new file mode 100644 index 0000000..b66f2f7 --- /dev/null +++ b/wcig_code_1_2/10/src/monkey/compiler/symbol_table_test.go @@ -0,0 +1,337 @@ +package compiler + +import "testing" + +func TestDefine(t *testing.T) { + expected := map[string]Symbol{ + "a": Symbol{Name: "a", Scope: GlobalScope, Index: 0}, + "b": Symbol{Name: "b", Scope: GlobalScope, Index: 1}, + "c": Symbol{Name: "c", Scope: LocalScope, Index: 0}, + "d": Symbol{Name: "d", Scope: LocalScope, Index: 1}, + "e": Symbol{Name: "e", Scope: LocalScope, Index: 0}, + "f": Symbol{Name: "f", Scope: LocalScope, Index: 1}, + } + + global := NewSymbolTable() + + a := global.Define("a") + if a != expected["a"] { + t.Errorf("expected a=%+v, got=%+v", expected["a"], a) + } + + b := global.Define("b") + if b != expected["b"] { + t.Errorf("expected b=%+v, got=%+v", expected["b"], b) + } + + firstLocal := NewEnclosedSymbolTable(global) + + c := firstLocal.Define("c") + if c != expected["c"] { + t.Errorf("expected c=%+v, got=%+v", expected["c"], c) + } + + d := firstLocal.Define("d") + if d != expected["d"] { + t.Errorf("expected d=%+v, got=%+v", expected["d"], d) + } + + secondLocal := NewEnclosedSymbolTable(firstLocal) + + e := secondLocal.Define("e") + if e != expected["e"] { + t.Errorf("expected e=%+v, got=%+v", expected["e"], e) + } + + f := secondLocal.Define("f") + if f != expected["f"] { + t.Errorf("expected f=%+v, got=%+v", expected["f"], f) + } +} + +func TestResolveGlobal(t *testing.T) { + global := NewSymbolTable() + global.Define("a") + global.Define("b") + + expected := []Symbol{ + Symbol{Name: "a", Scope: GlobalScope, Index: 0}, + Symbol{Name: "b", Scope: GlobalScope, Index: 1}, + } + + for _, sym := range expected { + result, ok := global.Resolve(sym.Name) + if !ok { + t.Errorf("name %s not resolvable", sym.Name) + continue + } + if result != sym { + t.Errorf("expected %s to resolve to %+v, got=%+v", + sym.Name, sym, result) + } + } +} + +func TestResolveLocal(t *testing.T) { + global := NewSymbolTable() + global.Define("a") + global.Define("b") + + local := NewEnclosedSymbolTable(global) + local.Define("c") + local.Define("d") + + expected := []Symbol{ + Symbol{Name: "a", Scope: GlobalScope, Index: 0}, + Symbol{Name: "b", Scope: GlobalScope, Index: 1}, + Symbol{Name: "c", Scope: LocalScope, Index: 0}, + Symbol{Name: "d", Scope: LocalScope, Index: 1}, + } + + for _, sym := range expected { + result, ok := local.Resolve(sym.Name) + if !ok { + t.Errorf("name %s not resolvable", sym.Name) + continue + } + if result != sym { + t.Errorf("expected %s to resolve to %+v, got=%+v", + sym.Name, sym, result) + } + } +} + +func TestResolveNestedLocal(t *testing.T) { + global := NewSymbolTable() + global.Define("a") + global.Define("b") + + firstLocal := NewEnclosedSymbolTable(global) + firstLocal.Define("c") + firstLocal.Define("d") + + secondLocal := NewEnclosedSymbolTable(firstLocal) + secondLocal.Define("e") + secondLocal.Define("f") + + tests := []struct { + table *SymbolTable + expectedSymbols []Symbol + }{ + { + firstLocal, + []Symbol{ + Symbol{Name: "a", Scope: GlobalScope, Index: 0}, + Symbol{Name: "b", Scope: GlobalScope, Index: 1}, + Symbol{Name: "c", Scope: LocalScope, Index: 0}, + Symbol{Name: "d", Scope: LocalScope, Index: 1}, + }, + }, + { + secondLocal, + []Symbol{ + Symbol{Name: "a", Scope: GlobalScope, Index: 0}, + Symbol{Name: "b", Scope: GlobalScope, Index: 1}, + Symbol{Name: "e", Scope: LocalScope, Index: 0}, + Symbol{Name: "f", Scope: LocalScope, Index: 1}, + }, + }, + } + + for _, tt := range tests { + for _, sym := range tt.expectedSymbols { + result, ok := tt.table.Resolve(sym.Name) + if !ok { + t.Errorf("name %s not resolvable", sym.Name) + continue + } + if result != sym { + t.Errorf("expected %s to resolve to %+v, got=%+v", + sym.Name, sym, result) + } + } + } +} + +func TestDefineResolveBuiltins(t *testing.T) { + global := NewSymbolTable() + firstLocal := NewEnclosedSymbolTable(global) + secondLocal := NewEnclosedSymbolTable(firstLocal) + + expected := []Symbol{ + Symbol{Name: "a", Scope: BuiltinScope, Index: 0}, + Symbol{Name: "c", Scope: BuiltinScope, Index: 1}, + Symbol{Name: "e", Scope: BuiltinScope, Index: 2}, + Symbol{Name: "f", Scope: BuiltinScope, Index: 3}, + } + + for i, v := range expected { + global.DefineBuiltin(i, v.Name) + } + + for _, table := range []*SymbolTable{global, firstLocal, secondLocal} { + for _, sym := range expected { + result, ok := table.Resolve(sym.Name) + if !ok { + t.Errorf("name %s not resolvable", sym.Name) + continue + } + if result != sym { + t.Errorf("expected %s to resolve to %+v, got=%+v", + sym.Name, sym, result) + } + } + } +} + +func TestResolveFree(t *testing.T) { + global := NewSymbolTable() + global.Define("a") + global.Define("b") + + firstLocal := NewEnclosedSymbolTable(global) + firstLocal.Define("c") + firstLocal.Define("d") + + secondLocal := NewEnclosedSymbolTable(firstLocal) + secondLocal.Define("e") + secondLocal.Define("f") + + tests := []struct { + table *SymbolTable + expectedSymbols []Symbol + expectedFreeSymbols []Symbol + }{ + { + firstLocal, + []Symbol{ + Symbol{Name: "a", Scope: GlobalScope, Index: 0}, + Symbol{Name: "b", Scope: GlobalScope, Index: 1}, + Symbol{Name: "c", Scope: LocalScope, Index: 0}, + Symbol{Name: "d", Scope: LocalScope, Index: 1}, + }, + []Symbol{}, + }, + { + secondLocal, + []Symbol{ + Symbol{Name: "a", Scope: GlobalScope, Index: 0}, + Symbol{Name: "b", Scope: GlobalScope, Index: 1}, + Symbol{Name: "c", Scope: FreeScope, Index: 0}, + Symbol{Name: "d", Scope: FreeScope, Index: 1}, + Symbol{Name: "e", Scope: LocalScope, Index: 0}, + Symbol{Name: "f", Scope: LocalScope, Index: 1}, + }, + []Symbol{ + Symbol{Name: "c", Scope: LocalScope, Index: 0}, + Symbol{Name: "d", Scope: LocalScope, Index: 1}, + }, + }, + } + + for _, tt := range tests { + for _, sym := range tt.expectedSymbols { + result, ok := tt.table.Resolve(sym.Name) + if !ok { + t.Errorf("name %s not resolvable", sym.Name) + continue + } + if result != sym { + t.Errorf("expected %s to resolve to %+v, got=%+v", + sym.Name, sym, result) + } + } + + if len(tt.table.FreeSymbols) != len(tt.expectedFreeSymbols) { + t.Errorf("wrong number of free symbols. got=%d, want=%d", + len(tt.table.FreeSymbols), len(tt.expectedFreeSymbols)) + continue + } + + for i, sym := range tt.expectedFreeSymbols { + result := tt.table.FreeSymbols[i] + if result != sym { + t.Errorf("wrong free symbol. got=%+v, want=%+v", + result, sym) + } + } + } +} + +func TestResolveUnresolvableFree(t *testing.T) { + global := NewSymbolTable() + global.Define("a") + + firstLocal := NewEnclosedSymbolTable(global) + firstLocal.Define("c") + + secondLocal := NewEnclosedSymbolTable(firstLocal) + secondLocal.Define("e") + secondLocal.Define("f") + + expected := []Symbol{ + Symbol{Name: "a", Scope: GlobalScope, Index: 0}, + Symbol{Name: "c", Scope: FreeScope, Index: 0}, + Symbol{Name: "e", Scope: LocalScope, Index: 0}, + Symbol{Name: "f", Scope: LocalScope, Index: 1}, + } + + for _, sym := range expected { + result, ok := secondLocal.Resolve(sym.Name) + if !ok { + t.Errorf("name %s not resolvable", sym.Name) + continue + } + if result != sym { + t.Errorf("expected %s to resolve to %+v, got=%+v", + sym.Name, sym, result) + } + } + + expectedUnresolvable := []string{ + "b", + "d", + } + + for _, name := range expectedUnresolvable { + _, ok := secondLocal.Resolve(name) + if ok { + t.Errorf("name %s resolved, but was expected not to", name) + } + } +} + +func TestDefineAndResolveFunctionName(t *testing.T) { + global := NewSymbolTable() + global.DefineFunctionName("a") + + expected := Symbol{Name: "a", Scope: FunctionScope, Index: 0} + + result, ok := global.Resolve(expected.Name) + if !ok { + t.Fatalf("function name %s not resolvable", expected.Name) + } + + if result != expected { + t.Errorf("expected %s to resolve to %+v, got=%+v", + expected.Name, expected, result) + } +} + +func TestShadowingFunctionName(t *testing.T) { + global := NewSymbolTable() + global.DefineFunctionName("a") + global.Define("a") + + expected := Symbol{Name: "a", Scope: GlobalScope, Index: 0} + + result, ok := global.Resolve(expected.Name) + if !ok { + t.Fatalf("function name %s not resolvable", expected.Name) + } + + if result != expected { + t.Errorf("expected %s to resolve to %+v, got=%+v", + expected.Name, expected, result) + } +} diff --git a/wcig_code_1_2/10/src/monkey/evaluator/builtins.go b/wcig_code_1_2/10/src/monkey/evaluator/builtins.go new file mode 100644 index 0000000..088ef5d --- /dev/null +++ b/wcig_code_1_2/10/src/monkey/evaluator/builtins.go @@ -0,0 +1,14 @@ +package evaluator + +import ( + "monkey/object" +) + +var builtins = map[string]*object.Builtin{ + "len": object.GetBuiltinByName("len"), + "puts": object.GetBuiltinByName("puts"), + "first": object.GetBuiltinByName("first"), + "last": object.GetBuiltinByName("last"), + "rest": object.GetBuiltinByName("rest"), + "push": object.GetBuiltinByName("push"), +} diff --git a/wcig_code_1_2/10/src/monkey/evaluator/evaluator.go b/wcig_code_1_2/10/src/monkey/evaluator/evaluator.go new file mode 100644 index 0000000..84c9011 --- /dev/null +++ b/wcig_code_1_2/10/src/monkey/evaluator/evaluator.go @@ -0,0 +1,445 @@ +package evaluator + +import ( + "fmt" + "monkey/ast" + "monkey/object" +) + +var ( + NULL = &object.Null{} + TRUE = &object.Boolean{Value: true} + FALSE = &object.Boolean{Value: false} +) + +func Eval(node ast.Node, env *object.Environment) object.Object { + switch node := node.(type) { + + // Statements + case *ast.Program: + return evalProgram(node, env) + + case *ast.BlockStatement: + return evalBlockStatement(node, env) + + case *ast.ExpressionStatement: + return Eval(node.Expression, env) + + case *ast.ReturnStatement: + val := Eval(node.ReturnValue, env) + if isError(val) { + return val + } + return &object.ReturnValue{Value: val} + + case *ast.LetStatement: + val := Eval(node.Value, env) + if isError(val) { + return val + } + env.Set(node.Name.Value, val) + + // Expressions + case *ast.IntegerLiteral: + return &object.Integer{Value: node.Value} + + case *ast.StringLiteral: + return &object.String{Value: node.Value} + + case *ast.Boolean: + return nativeBoolToBooleanObject(node.Value) + + case *ast.PrefixExpression: + right := Eval(node.Right, env) + if isError(right) { + return right + } + return evalPrefixExpression(node.Operator, right) + + case *ast.InfixExpression: + left := Eval(node.Left, env) + if isError(left) { + return left + } + + right := Eval(node.Right, env) + if isError(right) { + return right + } + + return evalInfixExpression(node.Operator, left, right) + + case *ast.IfExpression: + return evalIfExpression(node, env) + + case *ast.Identifier: + return evalIdentifier(node, env) + + case *ast.FunctionLiteral: + params := node.Parameters + body := node.Body + return &object.Function{Parameters: params, Env: env, Body: body} + + case *ast.CallExpression: + function := Eval(node.Function, env) + if isError(function) { + return function + } + + args := evalExpressions(node.Arguments, env) + if len(args) == 1 && isError(args[0]) { + return args[0] + } + + return applyFunction(function, args) + + case *ast.ArrayLiteral: + elements := evalExpressions(node.Elements, env) + if len(elements) == 1 && isError(elements[0]) { + return elements[0] + } + return &object.Array{Elements: elements} + + case *ast.IndexExpression: + left := Eval(node.Left, env) + if isError(left) { + return left + } + index := Eval(node.Index, env) + if isError(index) { + return index + } + return evalIndexExpression(left, index) + + case *ast.HashLiteral: + return evalHashLiteral(node, env) + + } + + return nil +} + +func evalProgram(program *ast.Program, env *object.Environment) object.Object { + var result object.Object + + for _, statement := range program.Statements { + result = Eval(statement, env) + + switch result := result.(type) { + case *object.ReturnValue: + return result.Value + case *object.Error: + return result + } + } + + return result +} + +func evalBlockStatement( + block *ast.BlockStatement, + env *object.Environment, +) object.Object { + var result object.Object + + for _, statement := range block.Statements { + result = Eval(statement, env) + + if result != nil { + rt := result.Type() + if rt == object.RETURN_VALUE_OBJ || rt == object.ERROR_OBJ { + return result + } + } + } + + return result +} + +func nativeBoolToBooleanObject(input bool) *object.Boolean { + if input { + return TRUE + } + return FALSE +} + +func evalPrefixExpression(operator string, right object.Object) object.Object { + switch operator { + case "!": + return evalBangOperatorExpression(right) + case "-": + return evalMinusPrefixOperatorExpression(right) + default: + return newError("unknown operator: %s%s", operator, right.Type()) + } +} + +func evalInfixExpression( + operator string, + left, right object.Object, +) object.Object { + switch { + case left.Type() == object.INTEGER_OBJ && right.Type() == object.INTEGER_OBJ: + return evalIntegerInfixExpression(operator, left, right) + case left.Type() == object.STRING_OBJ && right.Type() == object.STRING_OBJ: + return evalStringInfixExpression(operator, left, right) + case operator == "==": + return nativeBoolToBooleanObject(left == right) + case operator == "!=": + return nativeBoolToBooleanObject(left != right) + case left.Type() != right.Type(): + return newError("type mismatch: %s %s %s", + left.Type(), operator, right.Type()) + default: + return newError("unknown operator: %s %s %s", + left.Type(), operator, right.Type()) + } +} + +func evalBangOperatorExpression(right object.Object) object.Object { + switch right { + case TRUE: + return FALSE + case FALSE: + return TRUE + case NULL: + return TRUE + default: + return FALSE + } +} + +func evalMinusPrefixOperatorExpression(right object.Object) object.Object { + if right.Type() != object.INTEGER_OBJ { + return newError("unknown operator: -%s", right.Type()) + } + + value := right.(*object.Integer).Value + return &object.Integer{Value: -value} +} + +func evalIntegerInfixExpression( + operator string, + left, right object.Object, +) object.Object { + leftVal := left.(*object.Integer).Value + rightVal := right.(*object.Integer).Value + + switch operator { + case "+": + return &object.Integer{Value: leftVal + rightVal} + case "-": + return &object.Integer{Value: leftVal - rightVal} + case "*": + return &object.Integer{Value: leftVal * rightVal} + case "/": + return &object.Integer{Value: leftVal / rightVal} + case "<": + return nativeBoolToBooleanObject(leftVal < rightVal) + case ">": + return nativeBoolToBooleanObject(leftVal > rightVal) + case "==": + return nativeBoolToBooleanObject(leftVal == rightVal) + case "!=": + return nativeBoolToBooleanObject(leftVal != rightVal) + default: + return newError("unknown operator: %s %s %s", + left.Type(), operator, right.Type()) + } +} + +func evalStringInfixExpression( + operator string, + left, right object.Object, +) object.Object { + if operator != "+" { + return newError("unknown operator: %s %s %s", + left.Type(), operator, right.Type()) + } + + leftVal := left.(*object.String).Value + rightVal := right.(*object.String).Value + return &object.String{Value: leftVal + rightVal} +} + +func evalIfExpression( + ie *ast.IfExpression, + env *object.Environment, +) object.Object { + condition := Eval(ie.Condition, env) + if isError(condition) { + return condition + } + + if isTruthy(condition) { + return Eval(ie.Consequence, env) + } else if ie.Alternative != nil { + return Eval(ie.Alternative, env) + } else { + return NULL + } +} + +func evalIdentifier( + node *ast.Identifier, + env *object.Environment, +) object.Object { + if val, ok := env.Get(node.Value); ok { + return val + } + + if builtin, ok := builtins[node.Value]; ok { + return builtin + } + + return newError("identifier not found: " + node.Value) +} + +func isTruthy(obj object.Object) bool { + switch obj { + case NULL: + return false + case TRUE: + return true + case FALSE: + return false + default: + return true + } +} + +func newError(format string, a ...interface{}) *object.Error { + return &object.Error{Message: fmt.Sprintf(format, a...)} +} + +func isError(obj object.Object) bool { + if obj != nil { + return obj.Type() == object.ERROR_OBJ + } + return false +} + +func evalExpressions( + exps []ast.Expression, + env *object.Environment, +) []object.Object { + var result []object.Object + + for _, e := range exps { + evaluated := Eval(e, env) + if isError(evaluated) { + return []object.Object{evaluated} + } + result = append(result, evaluated) + } + + return result +} + +func applyFunction(fn object.Object, args []object.Object) object.Object { + switch fn := fn.(type) { + + case *object.Function: + extendedEnv := extendFunctionEnv(fn, args) + evaluated := Eval(fn.Body, extendedEnv) + return unwrapReturnValue(evaluated) + + case *object.Builtin: + if result := fn.Fn(args...); result != nil { + return result + } + return NULL + + default: + return newError("not a function: %s", fn.Type()) + } +} + +func extendFunctionEnv( + fn *object.Function, + args []object.Object, +) *object.Environment { + env := object.NewEnclosedEnvironment(fn.Env) + + for paramIdx, param := range fn.Parameters { + env.Set(param.Value, args[paramIdx]) + } + + return env +} + +func unwrapReturnValue(obj object.Object) object.Object { + if returnValue, ok := obj.(*object.ReturnValue); ok { + return returnValue.Value + } + + return obj +} + +func evalIndexExpression(left, index object.Object) object.Object { + switch { + case left.Type() == object.ARRAY_OBJ && index.Type() == object.INTEGER_OBJ: + return evalArrayIndexExpression(left, index) + case left.Type() == object.HASH_OBJ: + return evalHashIndexExpression(left, index) + default: + return newError("index operator not supported: %s", left.Type()) + } +} + +func evalArrayIndexExpression(array, index object.Object) object.Object { + arrayObject := array.(*object.Array) + idx := index.(*object.Integer).Value + max := int64(len(arrayObject.Elements) - 1) + + if idx < 0 || idx > max { + return NULL + } + + return arrayObject.Elements[idx] +} + +func evalHashLiteral( + node *ast.HashLiteral, + env *object.Environment, +) object.Object { + pairs := make(map[object.HashKey]object.HashPair) + + for keyNode, valueNode := range node.Pairs { + key := Eval(keyNode, env) + if isError(key) { + return key + } + + hashKey, ok := key.(object.Hashable) + if !ok { + return newError("unusable as hash key: %s", key.Type()) + } + + value := Eval(valueNode, env) + if isError(value) { + return value + } + + hashed := hashKey.HashKey() + pairs[hashed] = object.HashPair{Key: key, Value: value} + } + + return &object.Hash{Pairs: pairs} +} + +func evalHashIndexExpression(hash, index object.Object) object.Object { + hashObject := hash.(*object.Hash) + + key, ok := index.(object.Hashable) + if !ok { + return newError("unusable as hash key: %s", index.Type()) + } + + pair, ok := hashObject.Pairs[key.HashKey()] + if !ok { + return NULL + } + + return pair.Value +} diff --git a/wcig_code_1_2/10/src/monkey/evaluator/evaluator_test.go b/wcig_code_1_2/10/src/monkey/evaluator/evaluator_test.go new file mode 100644 index 0000000..2304e6f --- /dev/null +++ b/wcig_code_1_2/10/src/monkey/evaluator/evaluator_test.go @@ -0,0 +1,629 @@ +package evaluator + +import ( + "monkey/lexer" + "monkey/object" + "monkey/parser" + "testing" +) + +func TestEvalIntegerExpression(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"5", 5}, + {"10", 10}, + {"-5", -5}, + {"-10", -10}, + {"5 + 5 + 5 + 5 - 10", 10}, + {"2 * 2 * 2 * 2 * 2", 32}, + {"-50 + 100 + -50", 0}, + {"5 * 2 + 10", 20}, + {"5 + 2 * 10", 25}, + {"20 + 2 * -10", 0}, + {"50 / 2 * 2 + 10", 60}, + {"2 * (5 + 10)", 30}, + {"3 * 3 * 3 + 10", 37}, + {"3 * (3 * 3) + 10", 37}, + {"(5 + 10 * 2 + 15 / 3) * 2 + -10", 50}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testIntegerObject(t, evaluated, tt.expected) + } +} + +func TestEvalBooleanExpression(t *testing.T) { + tests := []struct { + input string + expected bool + }{ + {"true", true}, + {"false", false}, + {"1 < 2", true}, + {"1 > 2", false}, + {"1 < 1", false}, + {"1 > 1", false}, + {"1 == 1", true}, + {"1 != 1", false}, + {"1 == 2", false}, + {"1 != 2", true}, + {"true == true", true}, + {"false == false", true}, + {"true == false", false}, + {"true != false", true}, + {"false != true", true}, + {"(1 < 2) == true", true}, + {"(1 < 2) == false", false}, + {"(1 > 2) == true", false}, + {"(1 > 2) == false", true}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testBooleanObject(t, evaluated, tt.expected) + } +} + +func TestBangOperator(t *testing.T) { + tests := []struct { + input string + expected bool + }{ + {"!true", false}, + {"!false", true}, + {"!5", false}, + {"!!true", true}, + {"!!false", false}, + {"!!5", true}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testBooleanObject(t, evaluated, tt.expected) + } +} + +func TestIfElseExpressions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + {"if (true) { 10 }", 10}, + {"if (false) { 10 }", nil}, + {"if (1) { 10 }", 10}, + {"if (1 < 2) { 10 }", 10}, + {"if (1 > 2) { 10 }", nil}, + {"if (1 > 2) { 10 } else { 20 }", 20}, + {"if (1 < 2) { 10 } else { 20 }", 10}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + integer, ok := tt.expected.(int) + if ok { + testIntegerObject(t, evaluated, int64(integer)) + } else { + testNullObject(t, evaluated) + } + } +} + +func TestReturnStatements(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"return 10;", 10}, + {"return 10; 9;", 10}, + {"return 2 * 5; 9;", 10}, + {"9; return 2 * 5; 9;", 10}, + {"if (10 > 1) { return 10; }", 10}, + { + ` +if (10 > 1) { + if (10 > 1) { + return 10; + } + + return 1; +} +`, + 10, + }, + { + ` +let f = fn(x) { + return x; + x + 10; +}; +f(10);`, + 10, + }, + { + ` +let f = fn(x) { + let result = x + 10; + return result; + return 10; +}; +f(10);`, + 20, + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testIntegerObject(t, evaluated, tt.expected) + } +} + +func TestErrorHandling(t *testing.T) { + tests := []struct { + input string + expectedMessage string + }{ + { + "5 + true;", + "type mismatch: INTEGER + BOOLEAN", + }, + { + "5 + true; 5;", + "type mismatch: INTEGER + BOOLEAN", + }, + { + "-true", + "unknown operator: -BOOLEAN", + }, + { + "true + false;", + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + "true + false + true + false;", + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + "5; true + false; 5", + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + `"Hello" - "World"`, + "unknown operator: STRING - STRING", + }, + { + "if (10 > 1) { true + false; }", + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + ` +if (10 > 1) { + if (10 > 1) { + return true + false; + } + + return 1; +} +`, + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + "foobar", + "identifier not found: foobar", + }, + { + `{"name": "Monkey"}[fn(x) { x }];`, + "unusable as hash key: FUNCTION", + }, + { + `999[1]`, + "index operator not supported: INTEGER", + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + + errObj, ok := evaluated.(*object.Error) + if !ok { + t.Errorf("no error object returned. got=%T(%+v)", + evaluated, evaluated) + continue + } + + if errObj.Message != tt.expectedMessage { + t.Errorf("wrong error message. expected=%q, got=%q", + tt.expectedMessage, errObj.Message) + } + } +} + +func TestLetStatements(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"let a = 5; a;", 5}, + {"let a = 5 * 5; a;", 25}, + {"let a = 5; let b = a; b;", 5}, + {"let a = 5; let b = a; let c = a + b + 5; c;", 15}, + } + + for _, tt := range tests { + testIntegerObject(t, testEval(tt.input), tt.expected) + } +} + +func TestFunctionObject(t *testing.T) { + input := "fn(x) { x + 2; };" + + evaluated := testEval(input) + fn, ok := evaluated.(*object.Function) + if !ok { + t.Fatalf("object is not Function. got=%T (%+v)", evaluated, evaluated) + } + + if len(fn.Parameters) != 1 { + t.Fatalf("function has wrong parameters. Parameters=%+v", + fn.Parameters) + } + + if fn.Parameters[0].String() != "x" { + t.Fatalf("parameter is not 'x'. got=%q", fn.Parameters[0]) + } + + expectedBody := "(x + 2)" + + if fn.Body.String() != expectedBody { + t.Fatalf("body is not %q. got=%q", expectedBody, fn.Body.String()) + } +} + +func TestFunctionApplication(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"let identity = fn(x) { x; }; identity(5);", 5}, + {"let identity = fn(x) { return x; }; identity(5);", 5}, + {"let double = fn(x) { x * 2; }; double(5);", 10}, + {"let add = fn(x, y) { x + y; }; add(5, 5);", 10}, + {"let add = fn(x, y) { x + y; }; add(5 + 5, add(5, 5));", 20}, + {"fn(x) { x; }(5)", 5}, + } + + for _, tt := range tests { + testIntegerObject(t, testEval(tt.input), tt.expected) + } +} + +func TestEnclosingEnvironments(t *testing.T) { + input := ` +let first = 10; +let second = 10; +let third = 10; + +let ourFunction = fn(first) { + let second = 20; + + first + second + third; +}; + +ourFunction(20) + first + second;` + + testIntegerObject(t, testEval(input), 70) +} + +func TestClosures(t *testing.T) { + input := ` +let newAdder = fn(x) { + fn(y) { x + y }; +}; + +let addTwo = newAdder(2); +addTwo(2);` + + testIntegerObject(t, testEval(input), 4) +} + +func TestStringLiteral(t *testing.T) { + input := `"Hello World!"` + + evaluated := testEval(input) + str, ok := evaluated.(*object.String) + if !ok { + t.Fatalf("object is not String. got=%T (%+v)", evaluated, evaluated) + } + + if str.Value != "Hello World!" { + t.Errorf("String has wrong value. got=%q", str.Value) + } +} + +func TestStringConcatenation(t *testing.T) { + input := `"Hello" + " " + "World!"` + + evaluated := testEval(input) + str, ok := evaluated.(*object.String) + if !ok { + t.Fatalf("object is not String. got=%T (%+v)", evaluated, evaluated) + } + + if str.Value != "Hello World!" { + t.Errorf("String has wrong value. got=%q", str.Value) + } +} + +func TestBuiltinFunctions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + {`len("")`, 0}, + {`len("four")`, 4}, + {`len("hello world")`, 11}, + {`len(1)`, "argument to `len` not supported, got INTEGER"}, + {`len("one", "two")`, "wrong number of arguments. got=2, want=1"}, + {`len([1, 2, 3])`, 3}, + {`len([])`, 0}, + {`puts("hello", "world!")`, nil}, + {`first([1, 2, 3])`, 1}, + {`first([])`, nil}, + {`first(1)`, "argument to `first` must be ARRAY, got INTEGER"}, + {`last([1, 2, 3])`, 3}, + {`last([])`, nil}, + {`last(1)`, "argument to `last` must be ARRAY, got INTEGER"}, + {`rest([1, 2, 3])`, []int{2, 3}}, + {`rest([])`, nil}, + {`push([], 1)`, []int{1}}, + {`push(1, 1)`, "argument to `push` must be ARRAY, got INTEGER"}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + + switch expected := tt.expected.(type) { + case int: + testIntegerObject(t, evaluated, int64(expected)) + case nil: + testNullObject(t, evaluated) + case string: + errObj, ok := evaluated.(*object.Error) + if !ok { + t.Errorf("object is not Error. got=%T (%+v)", + evaluated, evaluated) + continue + } + if errObj.Message != expected { + t.Errorf("wrong error message. expected=%q, got=%q", + expected, errObj.Message) + } + case []int: + array, ok := evaluated.(*object.Array) + if !ok { + t.Errorf("obj not Array. got=%T (%+v)", evaluated, evaluated) + continue + } + + if len(array.Elements) != len(expected) { + t.Errorf("wrong num of elements. want=%d, got=%d", + len(expected), len(array.Elements)) + continue + } + + for i, expectedElem := range expected { + testIntegerObject(t, array.Elements[i], int64(expectedElem)) + } + } + } +} + +func TestArrayLiterals(t *testing.T) { + input := "[1, 2 * 2, 3 + 3]" + + evaluated := testEval(input) + result, ok := evaluated.(*object.Array) + if !ok { + t.Fatalf("object is not Array. got=%T (%+v)", evaluated, evaluated) + } + + if len(result.Elements) != 3 { + t.Fatalf("array has wrong num of elements. got=%d", + len(result.Elements)) + } + + testIntegerObject(t, result.Elements[0], 1) + testIntegerObject(t, result.Elements[1], 4) + testIntegerObject(t, result.Elements[2], 6) +} + +func TestArrayIndexExpressions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + { + "[1, 2, 3][0]", + 1, + }, + { + "[1, 2, 3][1]", + 2, + }, + { + "[1, 2, 3][2]", + 3, + }, + { + "let i = 0; [1][i];", + 1, + }, + { + "[1, 2, 3][1 + 1];", + 3, + }, + { + "let myArray = [1, 2, 3]; myArray[2];", + 3, + }, + { + "let myArray = [1, 2, 3]; myArray[0] + myArray[1] + myArray[2];", + 6, + }, + { + "let myArray = [1, 2, 3]; let i = myArray[0]; myArray[i]", + 2, + }, + { + "[1, 2, 3][3]", + nil, + }, + { + "[1, 2, 3][-1]", + nil, + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + integer, ok := tt.expected.(int) + if ok { + testIntegerObject(t, evaluated, int64(integer)) + } else { + testNullObject(t, evaluated) + } + } +} + +func TestHashLiterals(t *testing.T) { + input := `let two = "two"; + { + "one": 10 - 9, + two: 1 + 1, + "thr" + "ee": 6 / 2, + 4: 4, + true: 5, + false: 6 + }` + + evaluated := testEval(input) + result, ok := evaluated.(*object.Hash) + if !ok { + t.Fatalf("Eval didn't return Hash. got=%T (%+v)", evaluated, evaluated) + } + + expected := map[object.HashKey]int64{ + (&object.String{Value: "one"}).HashKey(): 1, + (&object.String{Value: "two"}).HashKey(): 2, + (&object.String{Value: "three"}).HashKey(): 3, + (&object.Integer{Value: 4}).HashKey(): 4, + TRUE.HashKey(): 5, + FALSE.HashKey(): 6, + } + + if len(result.Pairs) != len(expected) { + t.Fatalf("Hash has wrong num of pairs. got=%d", len(result.Pairs)) + } + + for expectedKey, expectedValue := range expected { + pair, ok := result.Pairs[expectedKey] + if !ok { + t.Errorf("no pair for given key in Pairs") + } + + testIntegerObject(t, pair.Value, expectedValue) + } +} + +func TestHashIndexExpressions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + { + `{"foo": 5}["foo"]`, + 5, + }, + { + `{"foo": 5}["bar"]`, + nil, + }, + { + `let key = "foo"; {"foo": 5}[key]`, + 5, + }, + { + `{}["foo"]`, + nil, + }, + { + `{5: 5}[5]`, + 5, + }, + { + `{true: 5}[true]`, + 5, + }, + { + `{false: 5}[false]`, + 5, + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + integer, ok := tt.expected.(int) + if ok { + testIntegerObject(t, evaluated, int64(integer)) + } else { + testNullObject(t, evaluated) + } + } +} +func testEval(input string) object.Object { + l := lexer.New(input) + p := parser.New(l) + program := p.ParseProgram() + env := object.NewEnvironment() + + return Eval(program, env) +} + +func testIntegerObject(t *testing.T, obj object.Object, expected int64) bool { + result, ok := obj.(*object.Integer) + if !ok { + t.Errorf("object is not Integer. got=%T (%+v)", obj, obj) + return false + } + if result.Value != expected { + t.Errorf("object has wrong value. got=%d, want=%d", + result.Value, expected) + return false + } + + return true +} + +func testBooleanObject(t *testing.T, obj object.Object, expected bool) bool { + result, ok := obj.(*object.Boolean) + if !ok { + t.Errorf("object is not Boolean. got=%T (%+v)", obj, obj) + return false + } + if result.Value != expected { + t.Errorf("object has wrong value. got=%t, want=%t", + result.Value, expected) + return false + } + return true +} + +func testNullObject(t *testing.T, obj object.Object) bool { + if obj != NULL { + t.Errorf("object is not NULL. got=%T (%+v)", obj, obj) + return false + } + return true +} diff --git a/wcig_code_1_2/10/src/monkey/go.mod b/wcig_code_1_2/10/src/monkey/go.mod new file mode 100644 index 0000000..3064854 --- /dev/null +++ b/wcig_code_1_2/10/src/monkey/go.mod @@ -0,0 +1,3 @@ +module monkey + +go 1.14 diff --git a/wcig_code_1_2/10/src/monkey/lexer/lexer.go b/wcig_code_1_2/10/src/monkey/lexer/lexer.go new file mode 100644 index 0000000..db4f5f7 --- /dev/null +++ b/wcig_code_1_2/10/src/monkey/lexer/lexer.go @@ -0,0 +1,157 @@ +package lexer + +import "monkey/token" + +type Lexer struct { + input string + position int // current position in input (points to current char) + readPosition int // current reading position in input (after current char) + ch byte // current char under examination +} + +func New(input string) *Lexer { + l := &Lexer{input: input} + l.readChar() + return l +} + +func (l *Lexer) NextToken() token.Token { + var tok token.Token + + l.skipWhitespace() + + switch l.ch { + case '=': + if l.peekChar() == '=' { + ch := l.ch + l.readChar() + literal := string(ch) + string(l.ch) + tok = token.Token{Type: token.EQ, Literal: literal} + } else { + tok = newToken(token.ASSIGN, l.ch) + } + case '+': + tok = newToken(token.PLUS, l.ch) + case '-': + tok = newToken(token.MINUS, l.ch) + case '!': + if l.peekChar() == '=' { + ch := l.ch + l.readChar() + literal := string(ch) + string(l.ch) + tok = token.Token{Type: token.NOT_EQ, Literal: literal} + } else { + tok = newToken(token.BANG, l.ch) + } + case '/': + tok = newToken(token.SLASH, l.ch) + case '*': + tok = newToken(token.ASTERISK, l.ch) + case '<': + tok = newToken(token.LT, l.ch) + case '>': + tok = newToken(token.GT, l.ch) + case ';': + tok = newToken(token.SEMICOLON, l.ch) + case ':': + tok = newToken(token.COLON, l.ch) + case ',': + tok = newToken(token.COMMA, l.ch) + case '{': + tok = newToken(token.LBRACE, l.ch) + case '}': + tok = newToken(token.RBRACE, l.ch) + case '(': + tok = newToken(token.LPAREN, l.ch) + case ')': + tok = newToken(token.RPAREN, l.ch) + case '"': + tok.Type = token.STRING + tok.Literal = l.readString() + case '[': + tok = newToken(token.LBRACKET, l.ch) + case ']': + tok = newToken(token.RBRACKET, l.ch) + case 0: + tok.Literal = "" + tok.Type = token.EOF + default: + if isLetter(l.ch) { + tok.Literal = l.readIdentifier() + tok.Type = token.LookupIdent(tok.Literal) + return tok + } else if isDigit(l.ch) { + tok.Type = token.INT + tok.Literal = l.readNumber() + return tok + } else { + tok = newToken(token.ILLEGAL, l.ch) + } + } + + l.readChar() + return tok +} + +func (l *Lexer) skipWhitespace() { + for l.ch == ' ' || l.ch == '\t' || l.ch == '\n' || l.ch == '\r' { + l.readChar() + } +} + +func (l *Lexer) readChar() { + if l.readPosition >= len(l.input) { + l.ch = 0 + } else { + l.ch = l.input[l.readPosition] + } + l.position = l.readPosition + l.readPosition += 1 +} + +func (l *Lexer) peekChar() byte { + if l.readPosition >= len(l.input) { + return 0 + } else { + return l.input[l.readPosition] + } +} + +func (l *Lexer) readIdentifier() string { + position := l.position + for isLetter(l.ch) { + l.readChar() + } + return l.input[position:l.position] +} + +func (l *Lexer) readNumber() string { + position := l.position + for isDigit(l.ch) { + l.readChar() + } + return l.input[position:l.position] +} + +func (l *Lexer) readString() string { + position := l.position + 1 + for { + l.readChar() + if l.ch == '"' || l.ch == 0 { + break + } + } + return l.input[position:l.position] +} + +func isLetter(ch byte) bool { + return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' +} + +func isDigit(ch byte) bool { + return '0' <= ch && ch <= '9' +} + +func newToken(tokenType token.TokenType, ch byte) token.Token { + return token.Token{Type: tokenType, Literal: string(ch)} +} diff --git a/wcig_code_1_2/10/src/monkey/lexer/lexer_test.go b/wcig_code_1_2/10/src/monkey/lexer/lexer_test.go new file mode 100644 index 0000000..e4e64a4 --- /dev/null +++ b/wcig_code_1_2/10/src/monkey/lexer/lexer_test.go @@ -0,0 +1,143 @@ +package lexer + +import ( + "testing" + + "monkey/token" +) + +func TestNextToken(t *testing.T) { + input := `let five = 5; +let ten = 10; + +let add = fn(x, y) { + x + y; +}; + +let result = add(five, ten); +!-/*5; +5 < 10 > 5; + +if (5 < 10) { + return true; +} else { + return false; +} + +10 == 10; +10 != 9; +"foobar" +"foo bar" +[1, 2]; +{"foo": "bar"} +` + + tests := []struct { + expectedType token.TokenType + expectedLiteral string + }{ + {token.LET, "let"}, + {token.IDENT, "five"}, + {token.ASSIGN, "="}, + {token.INT, "5"}, + {token.SEMICOLON, ";"}, + {token.LET, "let"}, + {token.IDENT, "ten"}, + {token.ASSIGN, "="}, + {token.INT, "10"}, + {token.SEMICOLON, ";"}, + {token.LET, "let"}, + {token.IDENT, "add"}, + {token.ASSIGN, "="}, + {token.FUNCTION, "fn"}, + {token.LPAREN, "("}, + {token.IDENT, "x"}, + {token.COMMA, ","}, + {token.IDENT, "y"}, + {token.RPAREN, ")"}, + {token.LBRACE, "{"}, + {token.IDENT, "x"}, + {token.PLUS, "+"}, + {token.IDENT, "y"}, + {token.SEMICOLON, ";"}, + {token.RBRACE, "}"}, + {token.SEMICOLON, ";"}, + {token.LET, "let"}, + {token.IDENT, "result"}, + {token.ASSIGN, "="}, + {token.IDENT, "add"}, + {token.LPAREN, "("}, + {token.IDENT, "five"}, + {token.COMMA, ","}, + {token.IDENT, "ten"}, + {token.RPAREN, ")"}, + {token.SEMICOLON, ";"}, + {token.BANG, "!"}, + {token.MINUS, "-"}, + {token.SLASH, "/"}, + {token.ASTERISK, "*"}, + {token.INT, "5"}, + {token.SEMICOLON, ";"}, + {token.INT, "5"}, + {token.LT, "<"}, + {token.INT, "10"}, + {token.GT, ">"}, + {token.INT, "5"}, + {token.SEMICOLON, ";"}, + {token.IF, "if"}, + {token.LPAREN, "("}, + {token.INT, "5"}, + {token.LT, "<"}, + {token.INT, "10"}, + {token.RPAREN, ")"}, + {token.LBRACE, "{"}, + {token.RETURN, "return"}, + {token.TRUE, "true"}, + {token.SEMICOLON, ";"}, + {token.RBRACE, "}"}, + {token.ELSE, "else"}, + {token.LBRACE, "{"}, + {token.RETURN, "return"}, + {token.FALSE, "false"}, + {token.SEMICOLON, ";"}, + {token.RBRACE, "}"}, + {token.INT, "10"}, + {token.EQ, "=="}, + {token.INT, "10"}, + {token.SEMICOLON, ";"}, + {token.INT, "10"}, + {token.NOT_EQ, "!="}, + {token.INT, "9"}, + {token.SEMICOLON, ";"}, + {token.STRING, "foobar"}, + {token.STRING, "foo bar"}, + {token.LBRACKET, "["}, + {token.INT, "1"}, + {token.COMMA, ","}, + {token.INT, "2"}, + {token.RBRACKET, "]"}, + {token.SEMICOLON, ";"}, + {token.LBRACE, "{"}, + {token.STRING, "foo"}, + {token.COLON, ":"}, + {token.STRING, "bar"}, + {token.RBRACE, "}"}, + {token.EOF, ""}, + } + + l := New(input) + + for i, tt := range tests { + tok := l.NextToken() + + if tok.Type != tt.expectedType { + t.Fatalf("tests[%d] - tokentype wrong. expected=%q, got=%q", + i, tt.expectedType, tok.Type) + } + + if tok.Literal != tt.expectedLiteral { + t.Fatalf("tests[%d] - literal wrong. expected=%q, got=%q", + i, tt.expectedLiteral, tok.Literal) + } + } +} diff --git a/wcig_code_1_2/10/src/monkey/main.go b/wcig_code_1_2/10/src/monkey/main.go new file mode 100644 index 0000000..1d27138 --- /dev/null +++ b/wcig_code_1_2/10/src/monkey/main.go @@ -0,0 +1,19 @@ +package main + +import ( + "fmt" + "monkey/repl" + "os" + "os/user" +) + +func main() { + user, err := user.Current() + if err != nil { + panic(err) + } + fmt.Printf("Hello %s! This is the Monkey programming language!\n", + user.Username) + fmt.Printf("Feel free to type in commands\n") + repl.Start(os.Stdin, os.Stdout) +} diff --git a/wcig_code_1_2/10/src/monkey/object/builtins.go b/wcig_code_1_2/10/src/monkey/object/builtins.go new file mode 100644 index 0000000..28dca2d --- /dev/null +++ b/wcig_code_1_2/10/src/monkey/object/builtins.go @@ -0,0 +1,143 @@ +package object + +import "fmt" + +var Builtins = []struct { + Name string + Builtin *Builtin +}{ + { + "len", + &Builtin{Fn: func(args ...Object) Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", + len(args)) + } + + switch arg := args[0].(type) { + case *Array: + return &Integer{Value: int64(len(arg.Elements))} + case *String: + return &Integer{Value: int64(len(arg.Value))} + default: + return newError("argument to `len` not supported, got %s", + args[0].Type()) + } + }, + }, + }, + { + "puts", + &Builtin{Fn: func(args ...Object) Object { + for _, arg := range args { + fmt.Println(arg.Inspect()) + } + + return nil + }, + }, + }, + { + "first", + &Builtin{Fn: func(args ...Object) Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", + len(args)) + } + if args[0].Type() != ARRAY_OBJ { + return newError("argument to `first` must be ARRAY, got %s", + args[0].Type()) + } + + arr := args[0].(*Array) + if len(arr.Elements) > 0 { + return arr.Elements[0] + } + + return nil + }, + }, + }, + { + "last", + &Builtin{Fn: func(args ...Object) Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", + len(args)) + } + if args[0].Type() != ARRAY_OBJ { + return newError("argument to `last` must be ARRAY, got %s", + args[0].Type()) + } + + arr := args[0].(*Array) + length := len(arr.Elements) + if length > 0 { + return arr.Elements[length-1] + } + + return nil + }, + }, + }, + { + "rest", + &Builtin{Fn: func(args ...Object) Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", + len(args)) + } + if args[0].Type() != ARRAY_OBJ { + return newError("argument to `rest` must be ARRAY, got %s", + args[0].Type()) + } + + arr := args[0].(*Array) + length := len(arr.Elements) + if length > 0 { + newElements := make([]Object, length-1, length-1) + copy(newElements, arr.Elements[1:length]) + return &Array{Elements: newElements} + } + + return nil + }, + }, + }, + { + "push", + &Builtin{Fn: func(args ...Object) Object { + if len(args) != 2 { + return newError("wrong number of arguments. got=%d, want=2", + len(args)) + } + if args[0].Type() != ARRAY_OBJ { + return newError("argument to `push` must be ARRAY, got %s", + args[0].Type()) + } + + arr := args[0].(*Array) + length := len(arr.Elements) + + newElements := make([]Object, length+1, length+1) + copy(newElements, arr.Elements) + newElements[length] = args[1] + + return &Array{Elements: newElements} + }, + }, + }, +} + +func newError(format string, a ...interface{}) *Error { + return &Error{Message: fmt.Sprintf(format, a...)} +} + +func GetBuiltinByName(name string) *Builtin { + for _, def := range Builtins { + if def.Name == name { + return def.Builtin + } + } + return nil +} diff --git a/wcig_code_1_2/10/src/monkey/object/environment.go b/wcig_code_1_2/10/src/monkey/object/environment.go new file mode 100644 index 0000000..6f31070 --- /dev/null +++ b/wcig_code_1_2/10/src/monkey/object/environment.go @@ -0,0 +1,30 @@ +package object + +func NewEnclosedEnvironment(outer *Environment) *Environment { + env := NewEnvironment() + env.outer = outer + return env +} + +func NewEnvironment() *Environment { + s := make(map[string]Object) + return &Environment{store: s, outer: nil} +} + +type Environment struct { + store map[string]Object + outer *Environment +} + +func (e *Environment) Get(name string) (Object, bool) { + obj, ok := e.store[name] + if !ok && e.outer != nil { + obj, ok = e.outer.Get(name) + } + return obj, ok +} + +func (e *Environment) Set(name string, val Object) Object { + e.store[name] = val + return val +} diff --git a/wcig_code_1_2/10/src/monkey/object/object.go b/wcig_code_1_2/10/src/monkey/object/object.go new file mode 100644 index 0000000..b5c22e4 --- /dev/null +++ b/wcig_code_1_2/10/src/monkey/object/object.go @@ -0,0 +1,208 @@ +package object + +import ( + "bytes" + "fmt" + "hash/fnv" + "monkey/ast" + "monkey/code" + "strings" +) + +type BuiltinFunction func(args ...Object) Object + +type ObjectType string + +const ( + NULL_OBJ = "NULL" + ERROR_OBJ = "ERROR" + + INTEGER_OBJ = "INTEGER" + BOOLEAN_OBJ = "BOOLEAN" + STRING_OBJ = "STRING" + + RETURN_VALUE_OBJ = "RETURN_VALUE" + + FUNCTION_OBJ = "FUNCTION" + BUILTIN_OBJ = "BUILTIN" + + ARRAY_OBJ = "ARRAY" + HASH_OBJ = "HASH" + + COMPILED_FUNCTION_OBJ = "COMPILED_FUNCTION_OBJ" + + CLOSURE_OBJ = "CLOSURE" +) + +type HashKey struct { + Type ObjectType + Value uint64 +} + +type Hashable interface { + HashKey() HashKey +} + +type Object interface { + Type() ObjectType + Inspect() string +} + +type Integer struct { + Value int64 +} + +func (i *Integer) Type() ObjectType { return INTEGER_OBJ } +func (i *Integer) Inspect() string { return fmt.Sprintf("%d", i.Value) } +func (i *Integer) HashKey() HashKey { + return HashKey{Type: i.Type(), Value: uint64(i.Value)} +} + +type Boolean struct { + Value bool +} + +func (b *Boolean) Type() ObjectType { return BOOLEAN_OBJ } +func (b *Boolean) Inspect() string { return fmt.Sprintf("%t", b.Value) } +func (b *Boolean) HashKey() HashKey { + var value uint64 + + if b.Value { + value = 1 + } else { + value = 0 + } + + return HashKey{Type: b.Type(), Value: value} +} + +type Null struct{} + +func (n *Null) Type() ObjectType { return NULL_OBJ } +func (n *Null) Inspect() string { return "null" } + +type ReturnValue struct { + Value Object +} + +func (rv *ReturnValue) Type() ObjectType { return RETURN_VALUE_OBJ } +func (rv *ReturnValue) Inspect() string { return rv.Value.Inspect() } + +type Error struct { + Message string +} + +func (e *Error) Type() ObjectType { return ERROR_OBJ } +func (e *Error) Inspect() string { return "ERROR: " + e.Message } + +type Function struct { + Parameters []*ast.Identifier + Body *ast.BlockStatement + Env *Environment +} + +func (f *Function) Type() ObjectType { return FUNCTION_OBJ } +func (f *Function) Inspect() string { + var out bytes.Buffer + + params := []string{} + for _, p := range f.Parameters { + params = append(params, p.String()) + } + + out.WriteString("fn") + out.WriteString("(") + out.WriteString(strings.Join(params, ", ")) + out.WriteString(") {\n") + out.WriteString(f.Body.String()) + out.WriteString("\n}") + + return out.String() +} + +type String struct { + Value string +} + +func (s *String) Type() ObjectType { return STRING_OBJ } +func (s *String) Inspect() string { return s.Value } +func (s *String) HashKey() HashKey { + h := fnv.New64a() + h.Write([]byte(s.Value)) + + return HashKey{Type: s.Type(), Value: h.Sum64()} +} + +type Builtin struct { + Fn BuiltinFunction +} + +func (b *Builtin) Type() ObjectType { return BUILTIN_OBJ } +func (b *Builtin) Inspect() string { return "builtin function" } + +type Array struct { + Elements []Object +} + +func (ao *Array) Type() ObjectType { return ARRAY_OBJ } +func (ao *Array) Inspect() string { + var out bytes.Buffer + + elements := []string{} + for _, e := range ao.Elements { + elements = append(elements, e.Inspect()) + } + + out.WriteString("[") + out.WriteString(strings.Join(elements, ", ")) + out.WriteString("]") + + return out.String() +} + +type HashPair struct { + Key Object + Value Object +} + +type Hash struct { + Pairs map[HashKey]HashPair +} + +func (h *Hash) Type() ObjectType { return HASH_OBJ } +func (h *Hash) Inspect() string { + var out bytes.Buffer + + pairs := []string{} + for _, pair := range h.Pairs { + pairs = append(pairs, fmt.Sprintf("%s: %s", + pair.Key.Inspect(), pair.Value.Inspect())) + } + + out.WriteString("{") + out.WriteString(strings.Join(pairs, ", ")) + out.WriteString("}") + + return out.String() +} + +type CompiledFunction struct { + Instructions code.Instructions + NumLocals int + NumParameters int +} + +func (cf *CompiledFunction) Type() ObjectType { return COMPILED_FUNCTION_OBJ } +func (cf *CompiledFunction) Inspect() string { + return fmt.Sprintf("CompiledFunction[%p]", cf) +} + +type Closure struct { + Fn *CompiledFunction + Free []Object +} + +func (c *Closure) Type() ObjectType { return CLOSURE_OBJ } +func (c *Closure) Inspect() string { + return fmt.Sprintf("Closure[%p]", c) +} diff --git a/wcig_code_1_2/10/src/monkey/object/object_test.go b/wcig_code_1_2/10/src/monkey/object/object_test.go new file mode 100644 index 0000000..63228a2 --- /dev/null +++ b/wcig_code_1_2/10/src/monkey/object/object_test.go @@ -0,0 +1,60 @@ +package object + +import "testing" + +func TestStringHashKey(t *testing.T) { + hello1 := &String{Value: "Hello World"} + hello2 := &String{Value: "Hello World"} + diff1 := &String{Value: "My name is johnny"} + diff2 := &String{Value: "My name is johnny"} + + if hello1.HashKey() != hello2.HashKey() { + t.Errorf("strings with same content have different hash keys") + } + + if diff1.HashKey() != diff2.HashKey() { + t.Errorf("strings with same content have different hash keys") + } + + if hello1.HashKey() == diff1.HashKey() { + t.Errorf("strings with different content have same hash keys") + } +} + +func TestBooleanHashKey(t *testing.T) { + true1 := &Boolean{Value: true} + true2 := &Boolean{Value: true} + false1 := &Boolean{Value: false} + false2 := &Boolean{Value: false} + + if true1.HashKey() != true2.HashKey() { + t.Errorf("trues do not have same hash key") + } + + if false1.HashKey() != false2.HashKey() { + t.Errorf("falses do not have same hash key") + } + + if true1.HashKey() == false1.HashKey() { + t.Errorf("true has same hash key as false") + } +} + +func TestIntegerHashKey(t *testing.T) { + one1 := &Integer{Value: 1} + one2 := &Integer{Value: 1} + two1 := &Integer{Value: 2} + two2 := &Integer{Value: 2} + + if one1.HashKey() != one2.HashKey() { + t.Errorf("integers with same content have twoerent hash keys") + } + + if two1.HashKey() != two2.HashKey() { + t.Errorf("integers with same content have twoerent hash keys") + } + + if one1.HashKey() == two1.HashKey() { + t.Errorf("integers with twoerent content have same hash keys") + } +} diff --git a/wcig_code_1_2/10/src/monkey/parser/parser.go b/wcig_code_1_2/10/src/monkey/parser/parser.go new file mode 100644 index 0000000..8a6e21d --- /dev/null +++ b/wcig_code_1_2/10/src/monkey/parser/parser.go @@ -0,0 +1,495 @@ +package parser + +import ( + "fmt" + "monkey/ast" + "monkey/lexer" + "monkey/token" + "strconv" +) + +const ( + _ int = iota + LOWEST + EQUALS // == + LESSGREATER // > or < + SUM // + + PRODUCT // * + PREFIX // -X or !X + CALL // myFunction(X) + INDEX // array[index] +) + +var precedences = map[token.TokenType]int{ + token.EQ: EQUALS, + token.NOT_EQ: EQUALS, + token.LT: LESSGREATER, + token.GT: LESSGREATER, + token.PLUS: SUM, + token.MINUS: SUM, + token.SLASH: PRODUCT, + token.ASTERISK: PRODUCT, + token.LPAREN: CALL, + token.LBRACKET: INDEX, +} + +type ( + prefixParseFn func() ast.Expression + infixParseFn func(ast.Expression) ast.Expression +) + +type Parser struct { + l *lexer.Lexer + errors []string + + curToken token.Token + peekToken token.Token + + prefixParseFns map[token.TokenType]prefixParseFn + infixParseFns map[token.TokenType]infixParseFn +} + +func New(l *lexer.Lexer) *Parser { + p := &Parser{ + l: l, + errors: []string{}, + } + + p.prefixParseFns = make(map[token.TokenType]prefixParseFn) + p.registerPrefix(token.IDENT, p.parseIdentifier) + p.registerPrefix(token.INT, p.parseIntegerLiteral) + p.registerPrefix(token.STRING, p.parseStringLiteral) + p.registerPrefix(token.BANG, p.parsePrefixExpression) + p.registerPrefix(token.MINUS, p.parsePrefixExpression) + p.registerPrefix(token.TRUE, p.parseBoolean) + p.registerPrefix(token.FALSE, p.parseBoolean) + p.registerPrefix(token.LPAREN, p.parseGroupedExpression) + p.registerPrefix(token.IF, p.parseIfExpression) + p.registerPrefix(token.FUNCTION, p.parseFunctionLiteral) + p.registerPrefix(token.LBRACKET, p.parseArrayLiteral) + p.registerPrefix(token.LBRACE, p.parseHashLiteral) + + p.infixParseFns = make(map[token.TokenType]infixParseFn) + p.registerInfix(token.PLUS, p.parseInfixExpression) + p.registerInfix(token.MINUS, p.parseInfixExpression) + p.registerInfix(token.SLASH, p.parseInfixExpression) + p.registerInfix(token.ASTERISK, p.parseInfixExpression) + p.registerInfix(token.EQ, p.parseInfixExpression) + p.registerInfix(token.NOT_EQ, p.parseInfixExpression) + p.registerInfix(token.LT, p.parseInfixExpression) + p.registerInfix(token.GT, p.parseInfixExpression) + + p.registerInfix(token.LPAREN, p.parseCallExpression) + p.registerInfix(token.LBRACKET, p.parseIndexExpression) + + // Read two tokens, so curToken and peekToken are both set + p.nextToken() + p.nextToken() + + return p +} + +func (p *Parser) nextToken() { + p.curToken = p.peekToken + p.peekToken = p.l.NextToken() +} + +func (p *Parser) curTokenIs(t token.TokenType) bool { + return p.curToken.Type == t +} + +func (p *Parser) peekTokenIs(t token.TokenType) bool { + return p.peekToken.Type == t +} + +func (p *Parser) expectPeek(t token.TokenType) bool { + if p.peekTokenIs(t) { + p.nextToken() + return true + } else { + p.peekError(t) + return false + } +} + +func (p *Parser) Errors() []string { + return p.errors +} + +func (p *Parser) peekError(t token.TokenType) { + msg := fmt.Sprintf("expected next token to be %s, got %s instead", + t, p.peekToken.Type) + p.errors = append(p.errors, msg) +} + +func (p *Parser) noPrefixParseFnError(t token.TokenType) { + msg := fmt.Sprintf("no prefix parse function for %s found", t) + p.errors = append(p.errors, msg) +} + +func (p *Parser) ParseProgram() *ast.Program { + program := &ast.Program{} + program.Statements = []ast.Statement{} + + for !p.curTokenIs(token.EOF) { + stmt := p.parseStatement() + if stmt != nil { + program.Statements = append(program.Statements, stmt) + } + p.nextToken() + } + + return program +} + +func (p *Parser) parseStatement() ast.Statement { + switch p.curToken.Type { + case token.LET: + return p.parseLetStatement() + case token.RETURN: + return p.parseReturnStatement() + default: + return p.parseExpressionStatement() + } +} + +func (p *Parser) parseLetStatement() *ast.LetStatement { + stmt := &ast.LetStatement{Token: p.curToken} + + if !p.expectPeek(token.IDENT) { + return nil + } + + stmt.Name = &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + + if !p.expectPeek(token.ASSIGN) { + return nil + } + + p.nextToken() + + stmt.Value = p.parseExpression(LOWEST) + + if fl, ok := stmt.Value.(*ast.FunctionLiteral); ok { + fl.Name = stmt.Name.Value + } + + if p.peekTokenIs(token.SEMICOLON) { + p.nextToken() + } + + return stmt +} + +func (p *Parser) parseReturnStatement() *ast.ReturnStatement { + stmt := &ast.ReturnStatement{Token: p.curToken} + + p.nextToken() + + stmt.ReturnValue = p.parseExpression(LOWEST) + + if p.peekTokenIs(token.SEMICOLON) { + p.nextToken() + } + + return stmt +} + +func (p *Parser) parseExpressionStatement() *ast.ExpressionStatement { + stmt := &ast.ExpressionStatement{Token: p.curToken} + + stmt.Expression = p.parseExpression(LOWEST) + + if p.peekTokenIs(token.SEMICOLON) { + p.nextToken() + } + + return stmt +} + +func (p *Parser) parseExpression(precedence int) ast.Expression { + prefix := p.prefixParseFns[p.curToken.Type] + if prefix == nil { + p.noPrefixParseFnError(p.curToken.Type) + return nil + } + leftExp := prefix() + + for !p.peekTokenIs(token.SEMICOLON) && precedence < p.peekPrecedence() { + infix := p.infixParseFns[p.peekToken.Type] + if infix == nil { + return leftExp + } + + p.nextToken() + + leftExp = infix(leftExp) + } + + return leftExp +} + +func (p *Parser) peekPrecedence() int { + if p, ok := precedences[p.peekToken.Type]; ok { + return p + } + + return LOWEST +} + +func (p *Parser) curPrecedence() int { + if p, ok := precedences[p.curToken.Type]; ok { + return p + } + + return LOWEST +} + +func (p *Parser) parseIdentifier() ast.Expression { + return &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} +} + +func (p *Parser) parseIntegerLiteral() ast.Expression { + lit := &ast.IntegerLiteral{Token: p.curToken} + + value, err := strconv.ParseInt(p.curToken.Literal, 0, 64) + if err != nil { + msg := fmt.Sprintf("could not parse %q as integer", p.curToken.Literal) + p.errors = append(p.errors, msg) + return nil + } + + lit.Value = value + + return lit +} + +func (p *Parser) parseStringLiteral() ast.Expression { + return &ast.StringLiteral{Token: p.curToken, Value: p.curToken.Literal} +} + +func (p *Parser) parsePrefixExpression() ast.Expression { + expression := &ast.PrefixExpression{ + Token: p.curToken, + Operator: p.curToken.Literal, + } + + p.nextToken() + + expression.Right = p.parseExpression(PREFIX) + + return expression +} + +func (p *Parser) parseInfixExpression(left ast.Expression) ast.Expression { + expression := &ast.InfixExpression{ + Token: p.curToken, + Operator: p.curToken.Literal, + Left: left, + } + + precedence := p.curPrecedence() + p.nextToken() + expression.Right = p.parseExpression(precedence) + + return expression +} + +func (p *Parser) parseBoolean() ast.Expression { + return &ast.Boolean{Token: p.curToken, Value: p.curTokenIs(token.TRUE)} +} + +func (p *Parser) parseGroupedExpression() ast.Expression { + p.nextToken() + + exp := p.parseExpression(LOWEST) + + if !p.expectPeek(token.RPAREN) { + return nil + } + + return exp +} + +func (p *Parser) parseIfExpression() ast.Expression { + expression := &ast.IfExpression{Token: p.curToken} + + if !p.expectPeek(token.LPAREN) { + return nil + } + + p.nextToken() + expression.Condition = p.parseExpression(LOWEST) + + if !p.expectPeek(token.RPAREN) { + return nil + } + + if !p.expectPeek(token.LBRACE) { + return nil + } + + expression.Consequence = p.parseBlockStatement() + + if p.peekTokenIs(token.ELSE) { + p.nextToken() + + if !p.expectPeek(token.LBRACE) { + return nil + } + + expression.Alternative = p.parseBlockStatement() + } + + return expression +} + +func (p *Parser) parseBlockStatement() *ast.BlockStatement { + block := &ast.BlockStatement{Token: p.curToken} + block.Statements = []ast.Statement{} + + p.nextToken() + + for !p.curTokenIs(token.RBRACE) && !p.curTokenIs(token.EOF) { + stmt := p.parseStatement() + if stmt != nil { + block.Statements = append(block.Statements, stmt) + } + p.nextToken() + } + + return block +} + +func (p *Parser) parseFunctionLiteral() ast.Expression { + lit := &ast.FunctionLiteral{Token: p.curToken} + + if !p.expectPeek(token.LPAREN) { + return nil + } + + lit.Parameters = p.parseFunctionParameters() + + if !p.expectPeek(token.LBRACE) { + return nil + } + + lit.Body = p.parseBlockStatement() + + return lit +} + +func (p *Parser) parseFunctionParameters() []*ast.Identifier { + identifiers := []*ast.Identifier{} + + if p.peekTokenIs(token.RPAREN) { + p.nextToken() + return identifiers + } + + p.nextToken() + + ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + identifiers = append(identifiers, ident) + + for p.peekTokenIs(token.COMMA) { + p.nextToken() + p.nextToken() + ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + identifiers = append(identifiers, ident) + } + + if !p.expectPeek(token.RPAREN) { + return nil + } + + return identifiers +} + +func (p *Parser) parseCallExpression(function ast.Expression) ast.Expression { + exp := &ast.CallExpression{Token: p.curToken, Function: function} + exp.Arguments = p.parseExpressionList(token.RPAREN) + return exp +} + +func (p *Parser) parseExpressionList(end token.TokenType) []ast.Expression { + list := []ast.Expression{} + + if p.peekTokenIs(end) { + p.nextToken() + return list + } + + p.nextToken() + list = append(list, p.parseExpression(LOWEST)) + + for p.peekTokenIs(token.COMMA) { + p.nextToken() + p.nextToken() + list = append(list, p.parseExpression(LOWEST)) + } + + if !p.expectPeek(end) { + return nil + } + + return list +} + +func (p *Parser) parseArrayLiteral() ast.Expression { + array := &ast.ArrayLiteral{Token: p.curToken} + + array.Elements = p.parseExpressionList(token.RBRACKET) + + return array +} + +func (p *Parser) parseIndexExpression(left ast.Expression) ast.Expression { + exp := &ast.IndexExpression{Token: p.curToken, Left: left} + + p.nextToken() + exp.Index = p.parseExpression(LOWEST) + + if !p.expectPeek(token.RBRACKET) { + return nil + } + + return exp +} + +func (p *Parser) parseHashLiteral() ast.Expression { + hash := &ast.HashLiteral{Token: p.curToken} + hash.Pairs = make(map[ast.Expression]ast.Expression) + + for !p.peekTokenIs(token.RBRACE) { + p.nextToken() + key := p.parseExpression(LOWEST) + + if !p.expectPeek(token.COLON) { + return nil + } + + p.nextToken() + value := p.parseExpression(LOWEST) + + hash.Pairs[key] = value + + if !p.peekTokenIs(token.RBRACE) && !p.expectPeek(token.COMMA) { + return nil + } + } + + if !p.expectPeek(token.RBRACE) { + return nil + } + + return hash +} + +func (p *Parser) registerPrefix(tokenType token.TokenType, fn prefixParseFn) { + p.prefixParseFns[tokenType] = fn +} + +func (p *Parser) registerInfix(tokenType token.TokenType, fn infixParseFn) { + p.infixParseFns[tokenType] = fn +} diff --git a/wcig_code_1_2/10/src/monkey/parser/parser_test.go b/wcig_code_1_2/10/src/monkey/parser/parser_test.go new file mode 100644 index 0000000..3430028 --- /dev/null +++ b/wcig_code_1_2/10/src/monkey/parser/parser_test.go @@ -0,0 +1,1114 @@ +package parser + +import ( + "fmt" + "monkey/ast" + "monkey/lexer" + "testing" +) + +func TestLetStatements(t *testing.T) { + tests := []struct { + input string + expectedIdentifier string + expectedValue interface{} + }{ + {"let x = 5;", "x", 5}, + {"let y = true;", "y", true}, + {"let foobar = y;", "foobar", "y"}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain 1 statements. got=%d", + len(program.Statements)) + } + + stmt := program.Statements[0] + if !testLetStatement(t, stmt, tt.expectedIdentifier) { + return + } + + val := stmt.(*ast.LetStatement).Value + if !testLiteralExpression(t, val, tt.expectedValue) { + return + } + } +} + +func TestReturnStatements(t *testing.T) { + tests := []struct { + input string + expectedValue interface{} + }{ + {"return 5;", 5}, + {"return true;", true}, + {"return foobar;", "foobar"}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain 1 statements. got=%d", + len(program.Statements)) + } + + stmt := program.Statements[0] + returnStmt, ok := stmt.(*ast.ReturnStatement) + if !ok { + t.Fatalf("stmt not *ast.returnStatement. got=%T", stmt) + } + if returnStmt.TokenLiteral() != "return" { + t.Fatalf("returnStmt.TokenLiteral not 'return', got %q", + returnStmt.TokenLiteral()) + } + if testLiteralExpression(t, returnStmt.ReturnValue, tt.expectedValue) { + return + } + } +} + +func TestIdentifierExpression(t *testing.T) { + input := "foobar;" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program has not enough statements. got=%d", + len(program.Statements)) + } + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + ident, ok := stmt.Expression.(*ast.Identifier) + if !ok { + t.Fatalf("exp not *ast.Identifier. got=%T", stmt.Expression) + } + if ident.Value != "foobar" { + t.Errorf("ident.Value not %s. got=%s", "foobar", ident.Value) + } + if ident.TokenLiteral() != "foobar" { + t.Errorf("ident.TokenLiteral not %s. got=%s", "foobar", + ident.TokenLiteral()) + } +} + +func TestIntegerLiteralExpression(t *testing.T) { + input := "5;" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program has not enough statements. got=%d", + len(program.Statements)) + } + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + literal, ok := stmt.Expression.(*ast.IntegerLiteral) + if !ok { + t.Fatalf("exp not *ast.IntegerLiteral. got=%T", stmt.Expression) + } + if literal.Value != 5 { + t.Errorf("literal.Value not %d. got=%d", 5, literal.Value) + } + if literal.TokenLiteral() != "5" { + t.Errorf("literal.TokenLiteral not %s. got=%s", "5", + literal.TokenLiteral()) + } +} + +func TestParsingPrefixExpressions(t *testing.T) { + prefixTests := []struct { + input string + operator string + value interface{} + }{ + {"!5;", "!", 5}, + {"-15;", "-", 15}, + {"!foobar;", "!", "foobar"}, + {"-foobar;", "-", "foobar"}, + {"!true;", "!", true}, + {"!false;", "!", false}, + } + + for _, tt := range prefixTests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.PrefixExpression) + if !ok { + t.Fatalf("stmt is not ast.PrefixExpression. got=%T", stmt.Expression) + } + if exp.Operator != tt.operator { + t.Fatalf("exp.Operator is not '%s'. got=%s", + tt.operator, exp.Operator) + } + if !testLiteralExpression(t, exp.Right, tt.value) { + return + } + } +} + +func TestParsingInfixExpressions(t *testing.T) { + infixTests := []struct { + input string + leftValue interface{} + operator string + rightValue interface{} + }{ + {"5 + 5;", 5, "+", 5}, + {"5 - 5;", 5, "-", 5}, + {"5 * 5;", 5, "*", 5}, + {"5 / 5;", 5, "/", 5}, + {"5 > 5;", 5, ">", 5}, + {"5 < 5;", 5, "<", 5}, + {"5 == 5;", 5, "==", 5}, + {"5 != 5;", 5, "!=", 5}, + {"foobar + barfoo;", "foobar", "+", "barfoo"}, + {"foobar - barfoo;", "foobar", "-", "barfoo"}, + {"foobar * barfoo;", "foobar", "*", "barfoo"}, + {"foobar / barfoo;", "foobar", "/", "barfoo"}, + {"foobar > barfoo;", "foobar", ">", "barfoo"}, + {"foobar < barfoo;", "foobar", "<", "barfoo"}, + {"foobar == barfoo;", "foobar", "==", "barfoo"}, + {"foobar != barfoo;", "foobar", "!=", "barfoo"}, + {"true == true", true, "==", true}, + {"true != false", true, "!=", false}, + {"false == false", false, "==", false}, + } + + for _, tt := range infixTests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + if !testInfixExpression(t, stmt.Expression, tt.leftValue, + tt.operator, tt.rightValue) { + return + } + } +} + +func TestOperatorPrecedenceParsing(t *testing.T) { + tests := []struct { + input string + expected string + }{ + { + "-a * b", + "((-a) * b)", + }, + { + "!-a", + "(!(-a))", + }, + { + "a + b + c", + "((a + b) + c)", + }, + { + "a + b - c", + "((a + b) - c)", + }, + { + "a * b * c", + "((a * b) * c)", + }, + { + "a * b / c", + "((a * b) / c)", + }, + { + "a + b / c", + "(a + (b / c))", + }, + { + "a + b * c + d / e - f", + "(((a + (b * c)) + (d / e)) - f)", + }, + { + "3 + 4; -5 * 5", + "(3 + 4)((-5) * 5)", + }, + { + "5 > 4 == 3 < 4", + "((5 > 4) == (3 < 4))", + }, + { + "5 < 4 != 3 > 4", + "((5 < 4) != (3 > 4))", + }, + { + "3 + 4 * 5 == 3 * 1 + 4 * 5", + "((3 + (4 * 5)) == ((3 * 1) + (4 * 5)))", + }, + { + "true", + "true", + }, + { + "false", + "false", + }, + { + "3 > 5 == false", + "((3 > 5) == false)", + }, + { + "3 < 5 == true", + "((3 < 5) == true)", + }, + { + "1 + (2 + 3) + 4", + "((1 + (2 + 3)) + 4)", + }, + { + "(5 + 5) * 2", + "((5 + 5) * 2)", + }, + { + "2 / (5 + 5)", + "(2 / (5 + 5))", + }, + { + "(5 + 5) * 2 * (5 + 5)", + "(((5 + 5) * 2) * (5 + 5))", + }, + { + "-(5 + 5)", + "(-(5 + 5))", + }, + { + "!(true == true)", + "(!(true == true))", + }, + { + "a + add(b * c) + d", + "((a + add((b * c))) + d)", + }, + { + "add(a, b, 1, 2 * 3, 4 + 5, add(6, 7 * 8))", + "add(a, b, 1, (2 * 3), (4 + 5), add(6, (7 * 8)))", + }, + { + "add(a + b + c * d / f + g)", + "add((((a + b) + ((c * d) / f)) + g))", + }, + { + "a * [1, 2, 3, 4][b * c] * d", + "((a * ([1, 2, 3, 4][(b * c)])) * d)", + }, + { + "add(a * b[2], b[1], 2 * [1, 2][1])", + "add((a * (b[2])), (b[1]), (2 * ([1, 2][1])))", + }, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + actual := program.String() + if actual != tt.expected { + t.Errorf("expected=%q, got=%q", tt.expected, actual) + } + } +} + +func TestBooleanExpression(t *testing.T) { + tests := []struct { + input string + expectedBoolean bool + }{ + {"true;", true}, + {"false;", false}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program has not enough statements. got=%d", + len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + boolean, ok := stmt.Expression.(*ast.Boolean) + if !ok { + t.Fatalf("exp not *ast.Boolean. got=%T", stmt.Expression) + } + if boolean.Value != tt.expectedBoolean { + t.Errorf("boolean.Value not %t. got=%t", tt.expectedBoolean, + boolean.Value) + } + } +} + +func TestIfExpression(t *testing.T) { + input := `if (x < y) { x }` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.IfExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.IfExpression. got=%T", + stmt.Expression) + } + + if !testInfixExpression(t, exp.Condition, "x", "<", "y") { + return + } + + if len(exp.Consequence.Statements) != 1 { + t.Errorf("consequence is not 1 statements. got=%d\n", + len(exp.Consequence.Statements)) + } + + consequence, ok := exp.Consequence.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", + exp.Consequence.Statements[0]) + } + + if !testIdentifier(t, consequence.Expression, "x") { + return + } + + if exp.Alternative != nil { + t.Errorf("exp.Alternative.Statements was not nil. got=%+v", exp.Alternative) + } +} + +func TestIfElseExpression(t *testing.T) { + input := `if (x < y) { x } else { y }` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.IfExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.IfExpression. got=%T", stmt.Expression) + } + + if !testInfixExpression(t, exp.Condition, "x", "<", "y") { + return + } + + if len(exp.Consequence.Statements) != 1 { + t.Errorf("consequence is not 1 statements. got=%d\n", + len(exp.Consequence.Statements)) + } + + consequence, ok := exp.Consequence.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", + exp.Consequence.Statements[0]) + } + + if !testIdentifier(t, consequence.Expression, "x") { + return + } + + if len(exp.Alternative.Statements) != 1 { + t.Errorf("exp.Alternative.Statements does not contain 1 statements. got=%d\n", + len(exp.Alternative.Statements)) + } + + alternative, ok := exp.Alternative.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", + exp.Alternative.Statements[0]) + } + + if !testIdentifier(t, alternative.Expression, "y") { + return + } +} + +func TestFunctionLiteralParsing(t *testing.T) { + input := `fn(x, y) { x + y; }` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + function, ok := stmt.Expression.(*ast.FunctionLiteral) + if !ok { + t.Fatalf("stmt.Expression is not ast.FunctionLiteral. got=%T", + stmt.Expression) + } + + if len(function.Parameters) != 2 { + t.Fatalf("function literal parameters wrong. want 2, got=%d\n", + len(function.Parameters)) + } + + testLiteralExpression(t, function.Parameters[0], "x") + testLiteralExpression(t, function.Parameters[1], "y") + + if len(function.Body.Statements) != 1 { + t.Fatalf("function.Body.Statements has not 1 statements. got=%d\n", + len(function.Body.Statements)) + } + + bodyStmt, ok := function.Body.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("function body stmt is not ast.ExpressionStatement. got=%T", + function.Body.Statements[0]) + } + + testInfixExpression(t, bodyStmt.Expression, "x", "+", "y") +} + +func TestFunctionParameterParsing(t *testing.T) { + tests := []struct { + input string + expectedParams []string + }{ + {input: "fn() {};", expectedParams: []string{}}, + {input: "fn(x) {};", expectedParams: []string{"x"}}, + {input: "fn(x, y, z) {};", expectedParams: []string{"x", "y", "z"}}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + function := stmt.Expression.(*ast.FunctionLiteral) + + if len(function.Parameters) != len(tt.expectedParams) { + t.Errorf("length parameters wrong. want %d, got=%d\n", + len(tt.expectedParams), len(function.Parameters)) + } + + for i, ident := range tt.expectedParams { + testLiteralExpression(t, function.Parameters[i], ident) + } + } +} + +func TestCallExpressionParsing(t *testing.T) { + input := "add(1, 2 * 3, 4 + 5);" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("stmt is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.CallExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.CallExpression. got=%T", + stmt.Expression) + } + + if !testIdentifier(t, exp.Function, "add") { + return + } + + if len(exp.Arguments) != 3 { + t.Fatalf("wrong length of arguments. got=%d", len(exp.Arguments)) + } + + testLiteralExpression(t, exp.Arguments[0], 1) + testInfixExpression(t, exp.Arguments[1], 2, "*", 3) + testInfixExpression(t, exp.Arguments[2], 4, "+", 5) +} + +func TestCallExpressionParameterParsing(t *testing.T) { + tests := []struct { + input string + expectedIdent string + expectedArgs []string + }{ + { + input: "add();", + expectedIdent: "add", + expectedArgs: []string{}, + }, + { + input: "add(1);", + expectedIdent: "add", + expectedArgs: []string{"1"}, + }, + { + input: "add(1, 2 * 3, 4 + 5);", + expectedIdent: "add", + expectedArgs: []string{"1", "(2 * 3)", "(4 + 5)"}, + }, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + exp, ok := stmt.Expression.(*ast.CallExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.CallExpression. got=%T", + stmt.Expression) + } + + if !testIdentifier(t, exp.Function, tt.expectedIdent) { + return + } + + if len(exp.Arguments) != len(tt.expectedArgs) { + t.Fatalf("wrong number of arguments. want=%d, got=%d", + len(tt.expectedArgs), len(exp.Arguments)) + } + + for i, arg := range tt.expectedArgs { + if exp.Arguments[i].String() != arg { + t.Errorf("argument %d wrong. want=%q, got=%q", i, + arg, exp.Arguments[i].String()) + } + } + } +} + +func TestStringLiteralExpression(t *testing.T) { + input := `"hello world";` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + literal, ok := stmt.Expression.(*ast.StringLiteral) + if !ok { + t.Fatalf("exp not *ast.StringLiteral. got=%T", stmt.Expression) + } + + if literal.Value != "hello world" { + t.Errorf("literal.Value not %q. got=%q", "hello world", literal.Value) + } +} + +func TestParsingEmptyArrayLiterals(t *testing.T) { + input := "[]" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + array, ok := stmt.Expression.(*ast.ArrayLiteral) + if !ok { + t.Fatalf("exp not ast.ArrayLiteral. got=%T", stmt.Expression) + } + + if len(array.Elements) != 0 { + t.Errorf("len(array.Elements) not 0. got=%d", len(array.Elements)) + } +} + +func TestParsingArrayLiterals(t *testing.T) { + input := "[1, 2 * 2, 3 + 3]" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + array, ok := stmt.Expression.(*ast.ArrayLiteral) + if !ok { + t.Fatalf("exp not ast.ArrayLiteral. got=%T", stmt.Expression) + } + + if len(array.Elements) != 3 { + t.Fatalf("len(array.Elements) not 3. got=%d", len(array.Elements)) + } + + testIntegerLiteral(t, array.Elements[0], 1) + testInfixExpression(t, array.Elements[1], 2, "*", 2) + testInfixExpression(t, array.Elements[2], 3, "+", 3) +} + +func TestParsingIndexExpressions(t *testing.T) { + input := "myArray[1 + 1]" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + indexExp, ok := stmt.Expression.(*ast.IndexExpression) + if !ok { + t.Fatalf("exp not *ast.IndexExpression. got=%T", stmt.Expression) + } + + if !testIdentifier(t, indexExp.Left, "myArray") { + return + } + + if !testInfixExpression(t, indexExp.Index, 1, "+", 1) { + return + } +} + +func TestParsingEmptyHashLiteral(t *testing.T) { + input := "{}" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + if len(hash.Pairs) != 0 { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } +} + +func TestParsingHashLiteralsStringKeys(t *testing.T) { + input := `{"one": 1, "two": 2, "three": 3}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + expected := map[string]int64{ + "one": 1, + "two": 2, + "three": 3, + } + + if len(hash.Pairs) != len(expected) { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } + + for key, value := range hash.Pairs { + literal, ok := key.(*ast.StringLiteral) + if !ok { + t.Errorf("key is not ast.StringLiteral. got=%T", key) + continue + } + + expectedValue := expected[literal.String()] + testIntegerLiteral(t, value, expectedValue) + } +} + +func TestParsingHashLiteralsBooleanKeys(t *testing.T) { + input := `{true: 1, false: 2}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + expected := map[string]int64{ + "true": 1, + "false": 2, + } + + if len(hash.Pairs) != len(expected) { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } + + for key, value := range hash.Pairs { + boolean, ok := key.(*ast.Boolean) + if !ok { + t.Errorf("key is not ast.BooleanLiteral. got=%T", key) + continue + } + + expectedValue := expected[boolean.String()] + testIntegerLiteral(t, value, expectedValue) + } +} + +func TestParsingHashLiteralsIntegerKeys(t *testing.T) { + input := `{1: 1, 2: 2, 3: 3}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + expected := map[string]int64{ + "1": 1, + "2": 2, + "3": 3, + } + + if len(hash.Pairs) != len(expected) { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } + + for key, value := range hash.Pairs { + integer, ok := key.(*ast.IntegerLiteral) + if !ok { + t.Errorf("key is not ast.IntegerLiteral. got=%T", key) + continue + } + + expectedValue := expected[integer.String()] + + testIntegerLiteral(t, value, expectedValue) + } +} + +func TestParsingHashLiteralsWithExpressions(t *testing.T) { + input := `{"one": 0 + 1, "two": 10 - 8, "three": 15 / 5}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + hash, ok := stmt.Expression.(*ast.HashLiteral) + if !ok { + t.Fatalf("exp is not ast.HashLiteral. got=%T", stmt.Expression) + } + + if len(hash.Pairs) != 3 { + t.Errorf("hash.Pairs has wrong length. got=%d", len(hash.Pairs)) + } + + tests := map[string]func(ast.Expression){ + "one": func(e ast.Expression) { + testInfixExpression(t, e, 0, "+", 1) + }, + "two": func(e ast.Expression) { + testInfixExpression(t, e, 10, "-", 8) + }, + "three": func(e ast.Expression) { + testInfixExpression(t, e, 15, "/", 5) + }, + } + + for key, value := range hash.Pairs { + literal, ok := key.(*ast.StringLiteral) + if !ok { + t.Errorf("key is not ast.StringLiteral. got=%T", key) + continue + } + + testFunc, ok := tests[literal.String()] + if !ok { + t.Errorf("No test function for key %q found", literal.String()) + continue + } + + testFunc(value) + } +} + +func TestFunctionLiteralWithName(t *testing.T) { + input := `let myFunction = fn() { };` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Body does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.LetStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.LetStatement. got=%T", + program.Statements[0]) + } + + function, ok := stmt.Value.(*ast.FunctionLiteral) + if !ok { + t.Fatalf("stmt.Value is not ast.FunctionLiteral. got=%T", + stmt.Value) + } + + if function.Name != "myFunction" { + t.Fatalf("function literal name wrong. want 'myFunction', got=%q\n", + function.Name) + } +} + +func testLetStatement(t *testing.T, s ast.Statement, name string) bool { + if s.TokenLiteral() != "let" { + t.Errorf("s.TokenLiteral not 'let'. got=%q", s.TokenLiteral()) + return false + } + + letStmt, ok := s.(*ast.LetStatement) + if !ok { + t.Errorf("s not *ast.LetStatement. got=%T", s) + return false + } + + if letStmt.Name.Value != name { + t.Errorf("letStmt.Name.Value not '%s'. got=%s", name, letStmt.Name.Value) + return false + } + + if letStmt.Name.TokenLiteral() != name { + t.Errorf("letStmt.Name.TokenLiteral() not '%s'. got=%s", + name, letStmt.Name.TokenLiteral()) + return false + } + + return true +} + +func testInfixExpression(t *testing.T, exp ast.Expression, left interface{}, + operator string, right interface{}) bool { + + opExp, ok := exp.(*ast.InfixExpression) + if !ok { + t.Errorf("exp is not ast.InfixExpression. got=%T(%s)", exp, exp) + return false + } + + if !testLiteralExpression(t, opExp.Left, left) { + return false + } + + if opExp.Operator != operator { + t.Errorf("exp.Operator is not '%s'. got=%q", operator, opExp.Operator) + return false + } + + if !testLiteralExpression(t, opExp.Right, right) { + return false + } + + return true +} + +func testLiteralExpression( + t *testing.T, + exp ast.Expression, + expected interface{}, +) bool { + switch v := expected.(type) { + case int: + return testIntegerLiteral(t, exp, int64(v)) + case int64: + return testIntegerLiteral(t, exp, v) + case string: + return testIdentifier(t, exp, v) + case bool: + return testBooleanLiteral(t, exp, v) + } + t.Errorf("type of exp not handled. got=%T", exp) + return false +} + +func testIntegerLiteral(t *testing.T, il ast.Expression, value int64) bool { + integ, ok := il.(*ast.IntegerLiteral) + if !ok { + t.Errorf("il not *ast.IntegerLiteral. got=%T", il) + return false + } + + if integ.Value != value { + t.Errorf("integ.Value not %d. got=%d", value, integ.Value) + return false + } + + if integ.TokenLiteral() != fmt.Sprintf("%d", value) { + t.Errorf("integ.TokenLiteral not %d. got=%s", value, + integ.TokenLiteral()) + return false + } + + return true +} + +func testIdentifier(t *testing.T, exp ast.Expression, value string) bool { + ident, ok := exp.(*ast.Identifier) + if !ok { + t.Errorf("exp not *ast.Identifier. got=%T", exp) + return false + } + + if ident.Value != value { + t.Errorf("ident.Value not %s. got=%s", value, ident.Value) + return false + } + + if ident.TokenLiteral() != value { + t.Errorf("ident.TokenLiteral not %s. got=%s", value, + ident.TokenLiteral()) + return false + } + + return true +} + +func testBooleanLiteral(t *testing.T, exp ast.Expression, value bool) bool { + bo, ok := exp.(*ast.Boolean) + if !ok { + t.Errorf("exp not *ast.Boolean. got=%T", exp) + return false + } + + if bo.Value != value { + t.Errorf("bo.Value not %t. got=%t", value, bo.Value) + return false + } + + if bo.TokenLiteral() != fmt.Sprintf("%t", value) { + t.Errorf("bo.TokenLiteral not %t. got=%s", + value, bo.TokenLiteral()) + return false + } + + return true +} + +func checkParserErrors(t *testing.T, p *Parser) { + errors := p.Errors() + if len(errors) == 0 { + return + } + + t.Errorf("parser has %d errors", len(errors)) + for _, msg := range errors { + t.Errorf("parser error: %q", msg) + } + t.FailNow() +} diff --git a/wcig_code_1_2/10/src/monkey/parser/parser_tracing.go b/wcig_code_1_2/10/src/monkey/parser/parser_tracing.go new file mode 100644 index 0000000..5fc569b --- /dev/null +++ b/wcig_code_1_2/10/src/monkey/parser/parser_tracing.go @@ -0,0 +1,32 @@ +package parser + +import ( + "fmt" + "strings" +) + +var traceLevel int = 0 + +const traceIdentPlaceholder string = "\t" + +func identLevel() string { + return strings.Repeat(traceIdentPlaceholder, traceLevel-1) +} + +func tracePrint(fs string) { + fmt.Printf("%s%s\n", identLevel(), fs) +} + +func incIdent() { traceLevel = traceLevel + 1 } +func decIdent() { traceLevel = traceLevel - 1 } + +func trace(msg string) string { + incIdent() + tracePrint("BEGIN " + msg) + return msg +} + +func untrace(msg string) { + tracePrint("END " + msg) + decIdent() +} diff --git a/wcig_code_1_2/10/src/monkey/repl/repl.go b/wcig_code_1_2/10/src/monkey/repl/repl.go new file mode 100644 index 0000000..dac5563 --- /dev/null +++ b/wcig_code_1_2/10/src/monkey/repl/repl.go @@ -0,0 +1,87 @@ +package repl + +import ( + "bufio" + "fmt" + "io" + "monkey/compiler" + "monkey/lexer" + "monkey/object" + "monkey/parser" + "monkey/vm" +) + +const PROMPT = ">> " + +func Start(in io.Reader, out io.Writer) { + scanner := bufio.NewScanner(in) + + constants := []object.Object{} + globals := make([]object.Object, vm.GlobalsSize) + + symbolTable := compiler.NewSymbolTable() + for i, v := range object.Builtins { + symbolTable.DefineBuiltin(i, v.Name) + } + + for { + fmt.Fprintf(out, PROMPT) + scanned := scanner.Scan() + if !scanned { + return + } + + line := scanner.Text() + l := lexer.New(line) + p := parser.New(l) + + program := p.ParseProgram() + if len(p.Errors()) != 0 { + printParserErrors(out, p.Errors()) + continue + } + + comp := compiler.NewWithState(symbolTable, constants) + err := comp.Compile(program) + if err != nil { + fmt.Fprintf(out, "Woops! Compilation failed:\n %s\n", err) + continue + } + + code := comp.Bytecode() + constants = code.Constants + + machine := vm.NewWithGlobalsStore(code, globals) + err = machine.Run() + if err != nil { + fmt.Fprintf(out, "Woops! Executing bytecode failed:\n %s\n", err) + continue + } + + lastPopped := machine.LastPoppedStackElem() + io.WriteString(out, lastPopped.Inspect()) + io.WriteString(out, "\n") + } +} + +const MONKEY_FACE = ` __,__ + .--. .-" "-. .--. + / .. \/ .-. .-. \/ .. \ + | | '| / Y \ |' | | + | \ \ \ 0 | 0 / / / | + \ '- ,\.-"""""""-./, -' / + ''-' /_ ^ ^ _\ '-'' + | \._ _./ | + \ \ '~' / / + '._ '-=-' _.' + '-----' +` + +func printParserErrors(out io.Writer, errors []string) { + io.WriteString(out, MONKEY_FACE) + io.WriteString(out, "Woops! We ran into some monkey business here!\n") + io.WriteString(out, " parser errors:\n") + for _, msg := range errors { + io.WriteString(out, "\t"+msg+"\n") + } +} diff --git a/wcig_code_1_2/10/src/monkey/token/token.go b/wcig_code_1_2/10/src/monkey/token/token.go new file mode 100644 index 0000000..3d2d2f7 --- /dev/null +++ b/wcig_code_1_2/10/src/monkey/token/token.go @@ -0,0 +1,70 @@ +package token + +type TokenType string + +const ( + ILLEGAL = "ILLEGAL" + EOF = "EOF" + + // Identifiers + literals + IDENT = "IDENT" // add, foobar, x, y, ... + INT = "INT" // 1343456 + STRING = "STRING" // "foobar" + + // Operators + ASSIGN = "=" + PLUS = "+" + MINUS = "-" + BANG = "!" + ASTERISK = "*" + SLASH = "/" + + LT = "<" + GT = ">" + + EQ = "==" + NOT_EQ = "!=" + + // Delimiters + COMMA = "," + SEMICOLON = ";" + COLON = ":" + + LPAREN = "(" + RPAREN = ")" + LBRACE = "{" + RBRACE = "}" + LBRACKET = "[" + RBRACKET = "]" + + // Keywords + FUNCTION = "FUNCTION" + LET = "LET" + TRUE = "TRUE" + FALSE = "FALSE" + IF = "IF" + ELSE = "ELSE" + RETURN = "RETURN" +) + +type Token struct { + Type TokenType + Literal string +} + +var keywords = map[string]TokenType{ + "fn": FUNCTION, + "let": LET, + "true": TRUE, + "false": FALSE, + "if": IF, + "else": ELSE, + "return": RETURN, +} + +func LookupIdent(ident string) TokenType { + if tok, ok := keywords[ident]; ok { + return tok + } + return IDENT +} diff --git a/wcig_code_1_2/10/src/monkey/vm/frame.go b/wcig_code_1_2/10/src/monkey/vm/frame.go new file mode 100644 index 0000000..2a11df5 --- /dev/null +++ b/wcig_code_1_2/10/src/monkey/vm/frame.go @@ -0,0 +1,26 @@ +package vm + +import ( + "monkey/code" + "monkey/object" +) + +type Frame struct { + cl *object.Closure + ip int + basePointer int +} + +func NewFrame(cl *object.Closure, basePointer int) *Frame { + f := &Frame{ + cl: cl, + ip: -1, + basePointer: basePointer, + } + + return f +} + +func (f *Frame) Instructions() code.Instructions { + return f.cl.Fn.Instructions +} diff --git a/wcig_code_1_2/10/src/monkey/vm/vm.go b/wcig_code_1_2/10/src/monkey/vm/vm.go new file mode 100644 index 0000000..3e25113 --- /dev/null +++ b/wcig_code_1_2/10/src/monkey/vm/vm.go @@ -0,0 +1,581 @@ +package vm + +import ( + "fmt" + "monkey/code" + "monkey/compiler" + "monkey/object" +) + +const StackSize = 2048 +const GlobalsSize = 65536 +const MaxFrames = 1024 + +var True = &object.Boolean{Value: true} +var False = &object.Boolean{Value: false} +var Null = &object.Null{} + +type VM struct { + constants []object.Object + + stack []object.Object + sp int // Always points to the next value. Top of stack is stack[sp-1] + + globals []object.Object + + frames []*Frame + framesIndex int +} + +func New(bytecode *compiler.Bytecode) *VM { + mainFn := &object.CompiledFunction{Instructions: bytecode.Instructions} + mainClosure := &object.Closure{Fn: mainFn} + mainFrame := NewFrame(mainClosure, 0) + + frames := make([]*Frame, MaxFrames) + frames[0] = mainFrame + + return &VM{ + constants: bytecode.Constants, + + stack: make([]object.Object, StackSize), + sp: 0, + + globals: make([]object.Object, GlobalsSize), + + frames: frames, + framesIndex: 1, + } +} + +func NewWithGlobalsStore(bytecode *compiler.Bytecode, s []object.Object) *VM { + vm := New(bytecode) + vm.globals = s + return vm +} + +func (vm *VM) LastPoppedStackElem() object.Object { + return vm.stack[vm.sp] +} + +func (vm *VM) Run() error { + var ip int + var ins code.Instructions + var op code.Opcode + + for vm.currentFrame().ip < len(vm.currentFrame().Instructions())-1 { + vm.currentFrame().ip++ + + ip = vm.currentFrame().ip + ins = vm.currentFrame().Instructions() + op = code.Opcode(ins[ip]) + + switch op { + case code.OpConstant: + constIndex := code.ReadUint16(ins[ip+1:]) + vm.currentFrame().ip += 2 + + err := vm.push(vm.constants[constIndex]) + if err != nil { + return err + } + + case code.OpPop: + vm.pop() + + case code.OpAdd, code.OpSub, code.OpMul, code.OpDiv: + err := vm.executeBinaryOperation(op) + if err != nil { + return err + } + + case code.OpTrue: + err := vm.push(True) + if err != nil { + return err + } + + case code.OpFalse: + err := vm.push(False) + if err != nil { + return err + } + + case code.OpEqual, code.OpNotEqual, code.OpGreaterThan: + err := vm.executeComparison(op) + if err != nil { + return err + } + + case code.OpBang: + err := vm.executeBangOperator() + if err != nil { + return err + } + + case code.OpMinus: + err := vm.executeMinusOperator() + if err != nil { + return err + } + + case code.OpJump: + pos := int(code.ReadUint16(ins[ip+1:])) + vm.currentFrame().ip = pos - 1 + + case code.OpJumpNotTruthy: + pos := int(code.ReadUint16(ins[ip+1:])) + vm.currentFrame().ip += 2 + + condition := vm.pop() + if !isTruthy(condition) { + vm.currentFrame().ip = pos - 1 + } + + case code.OpNull: + err := vm.push(Null) + if err != nil { + return err + } + + case code.OpSetGlobal: + globalIndex := code.ReadUint16(ins[ip+1:]) + vm.currentFrame().ip += 2 + + vm.globals[globalIndex] = vm.pop() + + case code.OpGetGlobal: + globalIndex := code.ReadUint16(ins[ip+1:]) + vm.currentFrame().ip += 2 + + err := vm.push(vm.globals[globalIndex]) + if err != nil { + return err + } + + case code.OpArray: + numElements := int(code.ReadUint16(ins[ip+1:])) + vm.currentFrame().ip += 2 + + array := vm.buildArray(vm.sp-numElements, vm.sp) + vm.sp = vm.sp - numElements + + err := vm.push(array) + if err != nil { + return err + } + + case code.OpHash: + numElements := int(code.ReadUint16(ins[ip+1:])) + vm.currentFrame().ip += 2 + + hash, err := vm.buildHash(vm.sp-numElements, vm.sp) + if err != nil { + return err + } + vm.sp = vm.sp - numElements + + err = vm.push(hash) + if err != nil { + return err + } + + case code.OpIndex: + index := vm.pop() + left := vm.pop() + + err := vm.executeIndexExpression(left, index) + if err != nil { + return err + } + + case code.OpCall: + numArgs := code.ReadUint8(ins[ip+1:]) + vm.currentFrame().ip += 1 + + err := vm.executeCall(int(numArgs)) + if err != nil { + return err + } + + case code.OpReturnValue: + returnValue := vm.pop() + + frame := vm.popFrame() + vm.sp = frame.basePointer - 1 + + err := vm.push(returnValue) + if err != nil { + return err + } + + case code.OpReturn: + frame := vm.popFrame() + vm.sp = frame.basePointer - 1 + + err := vm.push(Null) + if err != nil { + return err + } + + case code.OpSetLocal: + localIndex := code.ReadUint8(ins[ip+1:]) + vm.currentFrame().ip += 1 + + frame := vm.currentFrame() + + vm.stack[frame.basePointer+int(localIndex)] = vm.pop() + + case code.OpGetLocal: + localIndex := code.ReadUint8(ins[ip+1:]) + vm.currentFrame().ip += 1 + + frame := vm.currentFrame() + + err := vm.push(vm.stack[frame.basePointer+int(localIndex)]) + if err != nil { + return err + } + + case code.OpGetBuiltin: + builtinIndex := code.ReadUint8(ins[ip+1:]) + vm.currentFrame().ip += 1 + + definition := object.Builtins[builtinIndex] + + err := vm.push(definition.Builtin) + if err != nil { + return err + } + + case code.OpClosure: + constIndex := code.ReadUint16(ins[ip+1:]) + numFree := code.ReadUint8(ins[ip+3:]) + vm.currentFrame().ip += 3 + + err := vm.pushClosure(int(constIndex), int(numFree)) + if err != nil { + return err + } + + case code.OpGetFree: + freeIndex := code.ReadUint8(ins[ip+1:]) + vm.currentFrame().ip += 1 + + currentClosure := vm.currentFrame().cl + err := vm.push(currentClosure.Free[freeIndex]) + if err != nil { + return err + } + + case code.OpCurrentClosure: + currentClosure := vm.currentFrame().cl + err := vm.push(currentClosure) + if err != nil { + return err + } + } + } + + return nil +} + +func (vm *VM) push(o object.Object) error { + if vm.sp >= StackSize { + return fmt.Errorf("stack overflow") + } + + vm.stack[vm.sp] = o + vm.sp++ + + return nil +} + +func (vm *VM) pop() object.Object { + o := vm.stack[vm.sp-1] + vm.sp-- + return o +} + +func (vm *VM) executeBinaryOperation(op code.Opcode) error { + right := vm.pop() + left := vm.pop() + + leftType := left.Type() + rightType := right.Type() + + switch { + case leftType == object.INTEGER_OBJ && rightType == object.INTEGER_OBJ: + return vm.executeBinaryIntegerOperation(op, left, right) + case leftType == object.STRING_OBJ && rightType == object.STRING_OBJ: + return vm.executeBinaryStringOperation(op, left, right) + default: + return fmt.Errorf("unsupported types for binary operation: %s %s", + leftType, rightType) + } +} + +func (vm *VM) executeBinaryIntegerOperation( + op code.Opcode, + left, right object.Object, +) error { + leftValue := left.(*object.Integer).Value + rightValue := right.(*object.Integer).Value + + var result int64 + + switch op { + case code.OpAdd: + result = leftValue + rightValue + case code.OpSub: + result = leftValue - rightValue + case code.OpMul: + result = leftValue * rightValue + case code.OpDiv: + result = leftValue / rightValue + default: + return fmt.Errorf("unknown integer operator: %d", op) + } + + return vm.push(&object.Integer{Value: result}) +} + +func (vm *VM) executeComparison(op code.Opcode) error { + right := vm.pop() + left := vm.pop() + + if left.Type() == object.INTEGER_OBJ && right.Type() == object.INTEGER_OBJ { + return vm.executeIntegerComparison(op, left, right) + } + + switch op { + case code.OpEqual: + return vm.push(nativeBoolToBooleanObject(right == left)) + case code.OpNotEqual: + return vm.push(nativeBoolToBooleanObject(right != left)) + default: + return fmt.Errorf("unknown operator: %d (%s %s)", + op, left.Type(), right.Type()) + } +} + +func (vm *VM) executeIntegerComparison( + op code.Opcode, + left, right object.Object, +) error { + leftValue := left.(*object.Integer).Value + rightValue := right.(*object.Integer).Value + + switch op { + case code.OpEqual: + return vm.push(nativeBoolToBooleanObject(rightValue == leftValue)) + case code.OpNotEqual: + return vm.push(nativeBoolToBooleanObject(rightValue != leftValue)) + case code.OpGreaterThan: + return vm.push(nativeBoolToBooleanObject(leftValue > rightValue)) + default: + return fmt.Errorf("unknown operator: %d", op) + } +} + +func (vm *VM) executeBangOperator() error { + operand := vm.pop() + + switch operand { + case True: + return vm.push(False) + case False: + return vm.push(True) + case Null: + return vm.push(True) + default: + return vm.push(False) + } +} + +func (vm *VM) executeMinusOperator() error { + operand := vm.pop() + + if operand.Type() != object.INTEGER_OBJ { + return fmt.Errorf("unsupported type for negation: %s", operand.Type()) + } + + value := operand.(*object.Integer).Value + return vm.push(&object.Integer{Value: -value}) +} + +func (vm *VM) executeBinaryStringOperation( + op code.Opcode, + left, right object.Object, +) error { + if op != code.OpAdd { + return fmt.Errorf("unknown string operator: %d", op) + } + + leftValue := left.(*object.String).Value + rightValue := right.(*object.String).Value + + return vm.push(&object.String{Value: leftValue + rightValue}) +} + +func (vm *VM) buildArray(startIndex, endIndex int) object.Object { + elements := make([]object.Object, endIndex-startIndex) + + for i := startIndex; i < endIndex; i++ { + elements[i-startIndex] = vm.stack[i] + } + + return &object.Array{Elements: elements} +} + +func (vm *VM) buildHash(startIndex, endIndex int) (object.Object, error) { + hashedPairs := make(map[object.HashKey]object.HashPair) + + for i := startIndex; i < endIndex; i += 2 { + key := vm.stack[i] + value := vm.stack[i+1] + + pair := object.HashPair{Key: key, Value: value} + + hashKey, ok := key.(object.Hashable) + if !ok { + return nil, fmt.Errorf("unusable as hash key: %s", key.Type()) + } + + hashedPairs[hashKey.HashKey()] = pair + } + + return &object.Hash{Pairs: hashedPairs}, nil +} + +func (vm *VM) executeIndexExpression(left, index object.Object) error { + switch { + case left.Type() == object.ARRAY_OBJ && index.Type() == object.INTEGER_OBJ: + return vm.executeArrayIndex(left, index) + case left.Type() == object.HASH_OBJ: + return vm.executeHashIndex(left, index) + default: + return fmt.Errorf("index operator not supported: %s", left.Type()) + } +} + +func (vm *VM) executeArrayIndex(array, index object.Object) error { + arrayObject := array.(*object.Array) + i := index.(*object.Integer).Value + max := int64(len(arrayObject.Elements) - 1) + + if i < 0 || i > max { + return vm.push(Null) + } + + return vm.push(arrayObject.Elements[i]) +} + +func (vm *VM) executeHashIndex(hash, index object.Object) error { + hashObject := hash.(*object.Hash) + + key, ok := index.(object.Hashable) + if !ok { + return fmt.Errorf("unusable as hash key: %s", index.Type()) + } + + pair, ok := hashObject.Pairs[key.HashKey()] + if !ok { + return vm.push(Null) + } + + return vm.push(pair.Value) +} + +func (vm *VM) currentFrame() *Frame { + return vm.frames[vm.framesIndex-1] +} + +func (vm *VM) pushFrame(f *Frame) { + vm.frames[vm.framesIndex] = f + vm.framesIndex++ +} + +func (vm *VM) popFrame() *Frame { + vm.framesIndex-- + return vm.frames[vm.framesIndex] +} + +func (vm *VM) executeCall(numArgs int) error { + callee := vm.stack[vm.sp-1-numArgs] + switch callee := callee.(type) { + case *object.Closure: + return vm.callClosure(callee, numArgs) + case *object.Builtin: + return vm.callBuiltin(callee, numArgs) + default: + return fmt.Errorf("calling non-closure and non-builtin") + } +} + +func (vm *VM) callClosure(cl *object.Closure, numArgs int) error { + if numArgs != cl.Fn.NumParameters { + return fmt.Errorf("wrong number of arguments: want=%d, got=%d", + cl.Fn.NumParameters, numArgs) + } + + frame := NewFrame(cl, vm.sp-numArgs) + vm.pushFrame(frame) + + vm.sp = frame.basePointer + cl.Fn.NumLocals + + return nil +} + +func (vm *VM) callBuiltin(builtin *object.Builtin, numArgs int) error { + args := vm.stack[vm.sp-numArgs : vm.sp] + + result := builtin.Fn(args...) + vm.sp = vm.sp - numArgs - 1 + + if result != nil { + vm.push(result) + } else { + vm.push(Null) + } + + return nil +} + +func (vm *VM) pushClosure(constIndex, numFree int) error { + constant := vm.constants[constIndex] + function, ok := constant.(*object.CompiledFunction) + if !ok { + return fmt.Errorf("not a function: %+v", constant) + } + + free := make([]object.Object, numFree) + for i := 0; i < numFree; i++ { + free[i] = vm.stack[vm.sp-numFree+i] + } + vm.sp = vm.sp - numFree + + closure := &object.Closure{Fn: function, Free: free} + return vm.push(closure) +} + +func nativeBoolToBooleanObject(input bool) *object.Boolean { + if input { + return True + } + return False +} + +func isTruthy(obj object.Object) bool { + switch obj := obj.(type) { + + case *object.Boolean: + return obj.Value + + case *object.Null: + return false + + default: + return true + } +} diff --git a/wcig_code_1_2/10/src/monkey/vm/vm_test.go b/wcig_code_1_2/10/src/monkey/vm/vm_test.go new file mode 100644 index 0000000..5bebf8b --- /dev/null +++ b/wcig_code_1_2/10/src/monkey/vm/vm_test.go @@ -0,0 +1,785 @@ +package vm + +import ( + "fmt" + "monkey/ast" + "monkey/compiler" + "monkey/lexer" + "monkey/object" + "monkey/parser" + "testing" +) + +func TestIntegerArithmetic(t *testing.T) { + tests := []vmTestCase{ + {"1", 1}, + {"2", 2}, + {"1 + 2", 3}, + {"1 - 2", -1}, + {"1 * 2", 2}, + {"4 / 2", 2}, + {"50 / 2 * 2 + 10 - 5", 55}, + {"5 * (2 + 10)", 60}, + {"5 + 5 + 5 + 5 - 10", 10}, + {"2 * 2 * 2 * 2 * 2", 32}, + {"5 * 2 + 10", 20}, + {"5 + 2 * 10", 25}, + {"5 * (2 + 10)", 60}, + {"-5", -5}, + {"-10", -10}, + {"-50 + 100 + -50", 0}, + {"(5 + 10 * 2 + 15 / 3) * 2 + -10", 50}, + } + + runVmTests(t, tests) +} + +func TestBooleanExpressions(t *testing.T) { + tests := []vmTestCase{ + {"true", true}, + {"false", false}, + {"1 < 2", true}, + {"1 > 2", false}, + {"1 < 1", false}, + {"1 > 1", false}, + {"1 == 1", true}, + {"1 != 1", false}, + {"1 == 2", false}, + {"1 != 2", true}, + {"true == true", true}, + {"false == false", true}, + {"true == false", false}, + {"true != false", true}, + {"false != true", true}, + {"(1 < 2) == true", true}, + {"(1 < 2) == false", false}, + {"(1 > 2) == true", false}, + {"(1 > 2) == false", true}, + {"!true", false}, + {"!false", true}, + {"!5", false}, + {"!!true", true}, + {"!!false", false}, + {"!!5", true}, + {"!(if (false) { 5; })", true}, + } + + runVmTests(t, tests) +} + +func TestConditionals(t *testing.T) { + tests := []vmTestCase{ + {"if (true) { 10 }", 10}, + {"if (true) { 10 } else { 20 }", 10}, + {"if (false) { 10 } else { 20 } ", 20}, + {"if (1) { 10 }", 10}, + {"if (1 < 2) { 10 }", 10}, + {"if (1 < 2) { 10 } else { 20 }", 10}, + {"if (1 > 2) { 10 } else { 20 }", 20}, + {"if (1 > 2) { 10 }", Null}, + {"if (false) { 10 }", Null}, + {"if ((if (false) { 10 })) { 10 } else { 20 }", 20}, + } + + runVmTests(t, tests) +} + +func TestGlobalLetStatements(t *testing.T) { + tests := []vmTestCase{ + {"let one = 1; one", 1}, + {"let one = 1; let two = 2; one + two", 3}, + {"let one = 1; let two = one + one; one + two", 3}, + } + + runVmTests(t, tests) +} + +func TestStringExpressions(t *testing.T) { + tests := []vmTestCase{ + {`"monkey"`, "monkey"}, + {`"mon" + "key"`, "monkey"}, + {`"mon" + "key" + "banana"`, "monkeybanana"}, + } + + runVmTests(t, tests) +} + +func TestArrayLiterals(t *testing.T) { + tests := []vmTestCase{ + {"[]", []int{}}, + {"[1, 2, 3]", []int{1, 2, 3}}, + {"[1 + 2, 3 * 4, 5 + 6]", []int{3, 12, 11}}, + } + + runVmTests(t, tests) +} + +func TestHashLiterals(t *testing.T) { + tests := []vmTestCase{ + { + "{}", map[object.HashKey]int64{}, + }, + { + "{1: 2, 2: 3}", + map[object.HashKey]int64{ + (&object.Integer{Value: 1}).HashKey(): 2, + (&object.Integer{Value: 2}).HashKey(): 3, + }, + }, + { + "{1 + 1: 2 * 2, 3 + 3: 4 * 4}", + map[object.HashKey]int64{ + (&object.Integer{Value: 2}).HashKey(): 4, + (&object.Integer{Value: 6}).HashKey(): 16, + }, + }, + } + + runVmTests(t, tests) +} + +func TestIndexExpressions(t *testing.T) { + tests := []vmTestCase{ + {"[1, 2, 3][1]", 2}, + {"[1, 2, 3][0 + 2]", 3}, + {"[[1, 1, 1]][0][0]", 1}, + {"[][0]", Null}, + {"[1, 2, 3][99]", Null}, + {"[1][-1]", Null}, + {"{1: 1, 2: 2}[1]", 1}, + {"{1: 1, 2: 2}[2]", 2}, + {"{1: 1}[0]", Null}, + {"{}[0]", Null}, + } + + runVmTests(t, tests) +} + +func TestCallingFunctionsWithoutArguments(t *testing.T) { + tests := []vmTestCase{ + { + input: ` + let fivePlusTen = fn() { 5 + 10; }; + fivePlusTen(); + `, + expected: 15, + }, + { + input: ` + let one = fn() { 1; }; + let two = fn() { 2; }; + one() + two() + `, + expected: 3, + }, + { + input: ` + let a = fn() { 1 }; + let b = fn() { a() + 1 }; + let c = fn() { b() + 1 }; + c(); + `, + expected: 3, + }, + } + + runVmTests(t, tests) +} + +func TestFunctionsWithReturnStatement(t *testing.T) { + tests := []vmTestCase{ + { + input: ` + let earlyExit = fn() { return 99; 100; }; + earlyExit(); + `, + expected: 99, + }, + { + input: ` + let earlyExit = fn() { return 99; return 100; }; + earlyExit(); + `, + expected: 99, + }, + } + + runVmTests(t, tests) +} + +func TestFunctionsWithoutReturnValue(t *testing.T) { + tests := []vmTestCase{ + { + input: ` + let noReturn = fn() { }; + noReturn(); + `, + expected: Null, + }, + { + input: ` + let noReturn = fn() { }; + let noReturnTwo = fn() { noReturn(); }; + noReturn(); + noReturnTwo(); + `, + expected: Null, + }, + } + + runVmTests(t, tests) +} + +func TestFirstClassFunctions(t *testing.T) { + tests := []vmTestCase{ + { + input: ` + let returnsOne = fn() { 1; }; + let returnsOneReturner = fn() { returnsOne; }; + returnsOneReturner()(); + `, + expected: 1, + }, + { + input: ` + let returnsOneReturner = fn() { + let returnsOne = fn() { 1; }; + returnsOne; + }; + returnsOneReturner()(); + `, + expected: 1, + }, + } + + runVmTests(t, tests) +} + +func TestCallingFunctionsWithBindings(t *testing.T) { + tests := []vmTestCase{ + { + input: ` + let one = fn() { let one = 1; one }; + one(); + `, + expected: 1, + }, + { + input: ` + let oneAndTwo = fn() { let one = 1; let two = 2; one + two; }; + oneAndTwo(); + `, + expected: 3, + }, + { + input: ` + let oneAndTwo = fn() { let one = 1; let two = 2; one + two; }; + let threeAndFour = fn() { let three = 3; let four = 4; three + four; }; + oneAndTwo() + threeAndFour(); + `, + expected: 10, + }, + { + input: ` + let firstFoobar = fn() { let foobar = 50; foobar; }; + let secondFoobar = fn() { let foobar = 100; foobar; }; + firstFoobar() + secondFoobar(); + `, + expected: 150, + }, + { + input: ` + let globalSeed = 50; + let minusOne = fn() { + let num = 1; + globalSeed - num; + } + let minusTwo = fn() { + let num = 2; + globalSeed - num; + } + minusOne() + minusTwo(); + `, + expected: 97, + }, + } + + runVmTests(t, tests) +} + +func TestCallingFunctionsWithArgumentsAndBindings(t *testing.T) { + tests := []vmTestCase{ + { + input: ` + let identity = fn(a) { a; }; + identity(4); + `, + expected: 4, + }, + { + input: ` + let sum = fn(a, b) { a + b; }; + sum(1, 2); + `, + expected: 3, + }, + { + input: ` + let sum = fn(a, b) { + let c = a + b; + c; + }; + sum(1, 2); + `, + expected: 3, + }, + { + input: ` + let sum = fn(a, b) { + let c = a + b; + c; + }; + sum(1, 2) + sum(3, 4);`, + expected: 10, + }, + { + input: ` + let sum = fn(a, b) { + let c = a + b; + c; + }; + let outer = fn() { + sum(1, 2) + sum(3, 4); + }; + outer(); + `, + expected: 10, + }, + { + input: ` + let globalNum = 10; + + let sum = fn(a, b) { + let c = a + b; + c + globalNum; + }; + + let outer = fn() { + sum(1, 2) + sum(3, 4) + globalNum; + }; + + outer() + globalNum; + `, + expected: 50, + }, + } + + runVmTests(t, tests) +} + +func TestCallingFunctionsWithWrongArguments(t *testing.T) { + tests := []vmTestCase{ + { + input: `fn() { 1; }(1);`, + expected: `wrong number of arguments: want=0, got=1`, + }, + { + input: `fn(a) { a; }();`, + expected: `wrong number of arguments: want=1, got=0`, + }, + { + input: `fn(a, b) { a + b; }(1);`, + expected: `wrong number of arguments: want=2, got=1`, + }, + } + + for _, tt := range tests { + program := parse(tt.input) + + comp := compiler.New() + err := comp.Compile(program) + if err != nil { + t.Fatalf("compiler error: %s", err) + } + + vm := New(comp.Bytecode()) + err = vm.Run() + if err == nil { + t.Fatalf("expected VM error but resulted in none.") + } + + if err.Error() != tt.expected { + t.Fatalf("wrong VM error: want=%q, got=%q", tt.expected, err) + } + } +} + +func TestBuiltinFunctions(t *testing.T) { + tests := []vmTestCase{ + {`len("")`, 0}, + {`len("four")`, 4}, + {`len("hello world")`, 11}, + { + `len(1)`, + &object.Error{ + Message: "argument to `len` not supported, got INTEGER", + }, + }, + {`len("one", "two")`, + &object.Error{ + Message: "wrong number of arguments. got=2, want=1", + }, + }, + {`len([1, 2, 3])`, 3}, + {`len([])`, 0}, + {`puts("hello", "world!")`, Null}, + {`first([1, 2, 3])`, 1}, + {`first([])`, Null}, + {`first(1)`, + &object.Error{ + Message: "argument to `first` must be ARRAY, got INTEGER", + }, + }, + {`last([1, 2, 3])`, 3}, + {`last([])`, Null}, + {`last(1)`, + &object.Error{ + Message: "argument to `last` must be ARRAY, got INTEGER", + }, + }, + {`rest([1, 2, 3])`, []int{2, 3}}, + {`rest([])`, Null}, + {`push([], 1)`, []int{1}}, + {`push(1, 1)`, + &object.Error{ + Message: "argument to `push` must be ARRAY, got INTEGER", + }, + }, + } + + runVmTests(t, tests) +} + +func TestClosures(t *testing.T) { + tests := []vmTestCase{ + { + input: ` + let newClosure = fn(a) { + fn() { a; }; + }; + let closure = newClosure(99); + closure(); + `, + expected: 99, + }, + { + input: ` + let newAdder = fn(a, b) { + fn(c) { a + b + c }; + }; + let adder = newAdder(1, 2); + adder(8); + `, + expected: 11, + }, + { + input: ` + let newAdder = fn(a, b) { + let c = a + b; + fn(d) { c + d }; + }; + let adder = newAdder(1, 2); + adder(8); + `, + expected: 11, + }, + { + input: ` + let newAdderOuter = fn(a, b) { + let c = a + b; + fn(d) { + let e = d + c; + fn(f) { e + f; }; + }; + }; + let newAdderInner = newAdderOuter(1, 2) + let adder = newAdderInner(3); + adder(8); + `, + expected: 14, + }, + { + input: ` + let a = 1; + let newAdderOuter = fn(b) { + fn(c) { + fn(d) { a + b + c + d }; + }; + }; + let newAdderInner = newAdderOuter(2) + let adder = newAdderInner(3); + adder(8); + `, + expected: 14, + }, + { + input: ` + let newClosure = fn(a, b) { + let one = fn() { a; }; + let two = fn() { b; }; + fn() { one() + two(); }; + }; + let closure = newClosure(9, 90); + closure(); + `, + expected: 99, + }, + } + + runVmTests(t, tests) +} + +func TestRecursiveFunctions(t *testing.T) { + tests := []vmTestCase{ + { + input: ` + let countDown = fn(x) { + if (x == 0) { + return 0; + } else { + countDown(x - 1); + } + }; + countDown(1); + `, + expected: 0, + }, + { + input: ` + let countDown = fn(x) { + if (x == 0) { + return 0; + } else { + countDown(x - 1); + } + }; + let wrapper = fn() { + countDown(1); + }; + wrapper(); + `, + expected: 0, + }, + { + input: ` + let wrapper = fn() { + let countDown = fn(x) { + if (x == 0) { + return 0; + } else { + countDown(x - 1); + } + }; + countDown(1); + }; + wrapper(); + `, + expected: 0, + }, + } + + runVmTests(t, tests) +} + +func TestRecursiveFibonacci(t *testing.T) { + tests := []vmTestCase{ + { + input: ` + let fibonacci = fn(x) { + if (x == 0) { + return 0; + } else { + if (x == 1) { + return 1; + } else { + fibonacci(x - 1) + fibonacci(x - 2); + } + } + }; + fibonacci(15); + `, + expected: 610, + }, + } + + runVmTests(t, tests) +} + +type vmTestCase struct { + input string + expected interface{} +} + +func runVmTests(t *testing.T, tests []vmTestCase) { + t.Helper() + + for _, tt := range tests { + program := parse(tt.input) + + comp := compiler.New() + err := comp.Compile(program) + if err != nil { + t.Fatalf("compiler error: %s", err) + } + + vm := New(comp.Bytecode()) + err = vm.Run() + if err != nil { + t.Fatalf("vm error: %s", err) + } + + stackElem := vm.LastPoppedStackElem() + + testExpectedObject(t, tt.expected, stackElem) + } +} + +func parse(input string) *ast.Program { + l := lexer.New(input) + p := parser.New(l) + return p.ParseProgram() +} + +func testExpectedObject( + t *testing.T, + expected interface{}, + actual object.Object, +) { + t.Helper() + + switch expected := expected.(type) { + case int: + err := testIntegerObject(int64(expected), actual) + if err != nil { + t.Errorf("testIntegerObject failed: %s", err) + } + + case bool: + err := testBooleanObject(bool(expected), actual) + if err != nil { + t.Errorf("testBooleanObject failed: %s", err) + } + + case *object.Null: + if actual != Null { + t.Errorf("object is not Null: %T (%+v)", actual, actual) + } + + case string: + err := testStringObject(expected, actual) + if err != nil { + t.Errorf("testStringObject failed: %s", err) + } + + case []int: + array, ok := actual.(*object.Array) + if !ok { + t.Errorf("object not Array: %T (%+v)", actual, actual) + return + } + + if len(array.Elements) != len(expected) { + t.Errorf("wrong num of elements. want=%d, got=%d", + len(expected), len(array.Elements)) + return + } + + for i, expectedElem := range expected { + err := testIntegerObject(int64(expectedElem), array.Elements[i]) + if err != nil { + t.Errorf("testIntegerObject failed: %s", err) + } + } + + case map[object.HashKey]int64: + hash, ok := actual.(*object.Hash) + if !ok { + t.Errorf("object is not Hash. got=%T (%+v)", actual, actual) + return + } + + if len(hash.Pairs) != len(expected) { + t.Errorf("hash has wrong number of Pairs. want=%d, got=%d", + len(expected), len(hash.Pairs)) + return + } + + for expectedKey, expectedValue := range expected { + pair, ok := hash.Pairs[expectedKey] + if !ok { + t.Errorf("no pair for given key in Pairs") + } + + err := testIntegerObject(expectedValue, pair.Value) + if err != nil { + t.Errorf("testIntegerObject failed: %s", err) + } + } + + case *object.Error: + errObj, ok := actual.(*object.Error) + if !ok { + t.Errorf("object is not Error: %T (%+v)", actual, actual) + return + } + if errObj.Message != expected.Message { + t.Errorf("wrong error message. expected=%q, got=%q", + expected.Message, errObj.Message) + } + } +} + +func testIntegerObject(expected int64, actual object.Object) error { + result, ok := actual.(*object.Integer) + if !ok { + return fmt.Errorf("object is not Integer. got=%T (%+v)", + actual, actual) + } + + if result.Value != expected { + return fmt.Errorf("object has wrong value. got=%d, want=%d", + result.Value, expected) + } + + return nil +} + +func testBooleanObject(expected bool, actual object.Object) error { + result, ok := actual.(*object.Boolean) + if !ok { + return fmt.Errorf("object is not Boolean. got=%T (%+v)", + actual, actual) + } + + if result.Value != expected { + return fmt.Errorf("object has wrong value. got=%t, want=%t", + result.Value, expected) + } + + return nil +} + +func testStringObject(expected string, actual object.Object) error { + result, ok := actual.(*object.String) + if !ok { + return fmt.Errorf("object is not String. got=%T (%+v)", + actual, actual) + } + + if result.Value != expected { + return fmt.Errorf("object has wrong value. got=%q, want=%q", + result.Value, expected) + } + + return nil +} diff --git a/wcig_code_1_2/LICENSE b/wcig_code_1_2/LICENSE new file mode 100644 index 0000000..33794d9 --- /dev/null +++ b/wcig_code_1_2/LICENSE @@ -0,0 +1,24 @@ +------------------------------------------------------------------------------ +This folder (called the `code` folder) and its content, accompanying "Writing +A Compiler In Go", are licensed under the MIT License (MIT), which follows. +------------------------------------------------------------------------------ + +Copyright (c) 2018 Thorsten Ball + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.