From c88a5f506e0abc636b580291d967c61bfeedba03 Mon Sep 17 00:00:00 2001 From: daniel Date: Fri, 20 Dec 2019 11:40:38 -0800 Subject: [PATCH] some code clean up (#237) --- .circleci/config.yml | 47 + .goreleaser.yml | 9 - .travis.yml | 17 - Makefile | 5 +- README.md | 85 +- assert/assert.go | 356 -- assert/trace.go | 72 - builtins.go | 502 +++ bytecode.go | 292 ++ bytecode_test.go | 298 ++ cli/cli.go | 329 -- cli/cli_test.go | 64 - cmd/bench/main.go | 57 +- cmd/tengo/main.go | 294 +- cmd/tengomin/main.go | 31 - compiler.go | 1285 ++++++ compiler/ast/array_lit.go | 35 - compiler/ast/assign_stmt.go | 40 - compiler/ast/ast.go | 5 - compiler/ast/bad_expr.go | 25 - compiler/ast/bad_stmt.go | 25 - compiler/ast/binary_expr.go | 30 - compiler/ast/block_stmt.go | 35 - compiler/ast/bool_lit.go | 26 - compiler/ast/branch_stmt.go | 38 - compiler/ast/call_expr.go | 36 - compiler/ast/char_lit.go | 26 - compiler/ast/cond_expr.go | 30 - compiler/ast/empty_stmt.go | 29 - compiler/ast/error_expr.go | 29 - compiler/ast/export_stmt.go | 27 - compiler/ast/expr.go | 7 - compiler/ast/expr_stmt.go | 24 - compiler/ast/float_lit.go | 26 - compiler/ast/for_in_stmt.go | 32 - compiler/ast/for_stmt.go | 43 - compiler/ast/func_lit.go | 25 - compiler/ast/func_type.go | 25 - compiler/ast/ident.go | 29 - compiler/ast/if_stmt.go | 40 - compiler/ast/immutable_expr.go | 29 - compiler/ast/import_expr.go | 29 - compiler/ast/inc_dec_stmt.go | 29 - compiler/ast/index_expr.go | 32 - compiler/ast/int_lit.go | 26 - compiler/ast/map_element_lit.go | 27 - compiler/ast/map_lit.go | 35 - compiler/ast/node.go | 13 - compiler/ast/paren_expr.go | 26 - compiler/ast/return_stmt.go | 35 - compiler/ast/selector_expr.go | 25 - compiler/ast/slice_expr.go | 36 - compiler/ast/stmt.go | 7 - compiler/ast/string_lit.go | 26 - compiler/ast/unary_expr.go | 29 - compiler/ast/undefined_lit.go | 24 - compiler/bytecode.go | 90 - compiler/bytecode_decode.go | 97 - compiler/bytecode_optimize.go | 129 - compiler/bytecode_test.go | 289 -- compiler/compilation_scope.go | 11 - compiler/compiler.go | 846 ---- compiler/compiler_assign.go | 133 - compiler/compiler_error_report_test.go | 17 - compiler/compiler_for.go | 181 - compiler/compiler_logical.go | 30 - compiler/compiler_loops.go | 31 - compiler/compiler_module.go | 79 - compiler/compiler_optimize_test.go | 124 - compiler/compiler_scopes.go | 43 - compiler/compiler_scopes_test.go | 69 - compiler/compiler_test.go | 1068 ----- compiler/emitted_instruction.go | 8 - compiler/error.go | 20 - compiler/instructions_test.go | 65 - compiler/loop.go | 8 - compiler/module_loader.go | 4 - compiler/opcodes_test.go | 20 - compiler/parser/error.go | 21 - compiler/parser/error_list.go | 68 - compiler/parser/error_list_test.go | 18 - compiler/parser/error_test.go | 14 - compiler/parser/parse_source.go | 17 - compiler/parser/parser_array_test.go | 106 - compiler/parser/parser_assignment_test.go | 132 - compiler/parser/parser_boolean_test.go | 41 - compiler/parser/parser_call_test.go | 144 - compiler/parser/parser_char_test.go | 24 - compiler/parser/parser_cond_test.go | 48 - compiler/parser/parser_error_test.go | 43 - compiler/parser/parser_for_in_test.go | 66 - compiler/parser/parser_for_test.go | 118 - compiler/parser/parser_function_test.go | 86 - compiler/parser/parser_if_test.go | 215 - compiler/parser/parser_import_test.go | 46 - compiler/parser/parser_index_test.go | 98 - compiler/parser/parser_logical_test.go | 54 - compiler/parser/parser_map_test.go | 80 - compiler/parser/parser_precendence_test.go | 11 - compiler/parser/parser_selector_test.go | 165 - compiler/parser/parser_semicolon_test.go | 45 - compiler/parser/parser_string_test.go | 28 - compiler/parser/parser_test.go | 483 --- compiler/parser/sync.go | 12 - compiler/scanner/error_handler.go | 6 - compiler/scanner/mode.go | 10 - compiler/source/file.go | 110 - compiler/source/file_pos.go | 47 - compiler/source/file_set.go | 96 - compiler/symbol.go | 9 - compiler/symbol_scopes.go | 12 - compiler/symbol_table_test.go | 123 - compiler/token/keywords.go | 19 - compiler_test.go | 1340 +++++++ docs/builtins.md | 70 +- docs/formatting.md | 35 +- docs/interoperability.md | 100 +- docs/objects.md | 294 +- docs/operators.md | 6 +- docs/runtime-types.md | 24 +- docs/stdlib-base64.md | 9 +- docs/stdlib-enum.md | 41 +- docs/stdlib-fmt.md | 19 +- docs/stdlib-json.md | 13 +- docs/stdlib-math.md | 59 +- docs/stdlib-os.md | 130 +- docs/stdlib-rand.md | 56 +- docs/stdlib-text.md | 179 +- docs/stdlib-times.md | 89 +- docs/stdlib.md | 30 +- docs/tengo-cli.md | 12 +- docs/tutorial.md | 96 +- errors.go | 64 + objects/formatter.go => formatter.go | 147 +- go.mod | 2 +- go.sum | 17 + compiler/ast/ident_list.go => internal/ast.go | 36 +- .../ast_test.go | 18 +- .../symbol_table.go => internal/compile.go | 75 +- internal/expr.go | 597 +++ {compiler/ast => internal}/file.go | 15 +- .../instructions.go => internal/internal.go | 39 +- {compiler => internal}/opcodes.go | 11 +- {compiler/parser => internal}/parser.go | 520 ++- internal/parser_test.go | 2070 ++++++++++ {compiler/source => internal}/pos.go | 2 +- internal/require/require.go | 378 ++ {compiler/scanner => internal}/scanner.go | 139 +- .../scanner => internal}/scanner_test.go | 57 +- internal/source_file.go | 231 ++ internal/stmt.go | 349 ++ internal/symbol_table_test.go | 133 + {compiler => internal}/token/tokens.go | 17 + iterator.go | 209 + objects/module_map.go => modules.go | 34 +- objects.go | 1581 ++++++++ objects/array.go | 130 - objects/array_iterator.go | 57 - objects/array_test.go | 54 - objects/bool.go | 64 - objects/builtin_append.go | 21 - objects/builtin_convert.go | 169 - objects/builtin_copy.go | 9 - objects/builtin_format.go | 27 - objects/builtin_function.go | 47 - objects/builtin_len.go | 29 - objects/builtin_module.go | 23 - objects/builtin_type.go | 9 - objects/builtin_type_checks.go | 195 - objects/builtins.go | 118 - objects/bytes.go | 89 - objects/bytes_iterator.go | 57 - objects/callable.go | 9 - objects/callable_func.go | 4 - objects/char.go | 119 - objects/closure.go | 45 - objects/compiled_function.go | 62 - objects/conversion.go | 276 -- objects/count_objects.go | 31 - objects/count_objects_test.go | 66 - objects/error.go | 47 - objects/error_test.go | 19 - objects/errors.go | 38 - objects/float.go | 146 - objects/float_test.go | 126 - objects/immutable_array.go | 109 - objects/immutable_map.go | 105 - objects/importable.go | 7 - objects/index_assignable.go | 9 - objects/indexable.go | 9 - objects/int.go | 198 - objects/int_test.go | 199 - objects/iterable.go | 7 - objects/iterator.go | 15 - objects/map.go | 118 - objects/map_iterator.go | 62 - objects/map_test.go | 21 - objects/object.go | 30 - objects/object_ptr.go | 41 - objects/object_test.go | 168 - objects/objects.go | 12 - objects/objects_test.go | 25 - objects/source_module.go | 11 - objects/string.go | 103 - objects/string_iterator.go | 57 - objects/string_test.go | 23 - objects/time.go | 89 - objects/undefined.go | 62 - objects/user_function.go | 48 - objects_test.go | 736 ++++ runtime/errors.go | 11 - runtime/frame.go | 13 - runtime/vm_array_test.go | 55 - runtime/vm_assignment_test.go | 267 -- runtime/vm_bitwise_test.go | 36 - runtime/vm_boolean_test.go | 55 - runtime/vm_builtin_test.go | 186 - runtime/vm_bytes_test.go | 18 - runtime/vm_call_test.go | 19 - runtime/vm_char_test.go | 23 - runtime/vm_cond_test.go | 45 - runtime/vm_equality_test.go | 50 - runtime/vm_error_report_test.go | 49 - runtime/vm_error_test.go | 21 - runtime/vm_float_test.go | 15 - runtime/vm_for_in_test.go | 25 - runtime/vm_for_test.go | 238 -- runtime/vm_function_test.go | 298 -- runtime/vm_if_test.go | 103 - runtime/vm_immutable_test.go | 54 - runtime/vm_inc_dec_test.go | 21 - runtime/vm_indexable_test.go | 268 -- runtime/vm_integer_test.go | 30 - runtime/vm_iterable_test.go | 44 - runtime/vm_logic_test.go | 41 - runtime/vm_map_test.go | 51 - runtime/vm_module_test.go | 214 - runtime/vm_not_operator_test.go | 15 - runtime/vm_objects_limit_test.go | 49 - runtime/vm_return_test.go | 24 - runtime/vm_scopes_test.go | 103 - runtime/vm_selector_test.go | 90 - runtime/vm_source_modules_test.go | 142 - runtime/vm_srcmod_test.go | 49 - runtime/vm_stack_overflow_test.go | 7 - runtime/vm_string_test.go | 73 - runtime/vm_tail_call_test.go | 111 - runtime/vm_test.go | 385 -- runtime/vm_undefined_test.go | 21 - script.go | 313 ++ script/compiled.go | 159 - script/compiled_test.go | 168 - script/script.go | 185 - script/script_concurrency_test.go | 99 - script/script_custom_test.go | 101 - script/script_module_test.go | 55 - script/script_test.go | 126 - script/variable_test.go | 82 - script_test.go | 548 +++ stdlib/base64.go | 40 +- stdlib/base64_test.go | 21 +- stdlib/builtin_modules.go | 22 +- stdlib/errors.go | 11 +- stdlib/fmt.go | 59 +- stdlib/fmt_test.go | 16 +- stdlib/func_typedefs.go | 859 ++-- stdlib/func_typedefs_test.go | 574 +-- stdlib/gensrcmods.go | 6 +- stdlib/hex.go | 9 +- stdlib/hex_test.go | 7 +- stdlib/json.go | 94 +- stdlib/json/decode.go | 54 +- stdlib/json/encode.go | 29 +- stdlib/json/json_test.go | 46 +- stdlib/json/scanner.go | 7 +- stdlib/json_test.go | 95 +- stdlib/math.go | 291 +- stdlib/os.go | 480 ++- stdlib/os_exec.go | 86 +- stdlib/os_file.go | 93 +- stdlib/os_process.go | 64 +- stdlib/os_test.go | 61 +- stdlib/rand.go | 126 +- stdlib/rand_test.go | 16 +- stdlib/stdlib.go | 10 +- stdlib/stdlib_test.go | 118 +- stdlib/text.go | 584 +-- stdlib/text_regexp.go | 139 +- stdlib/text_test.go | 99 +- stdlib/times.go | 704 ++-- stdlib/times_test.go | 60 +- tengo.go | 303 +- tengo_test.go | 137 + script/variable.go => variable.go | 89 +- variable_test.go | 81 + runtime/vm.go => vm.go | 695 ++-- vm_test.go | 3475 +++++++++++++++++ 297 files changed, 19913 insertions(+), 19430 deletions(-) create mode 100644 .circleci/config.yml delete mode 100644 .travis.yml delete mode 100644 assert/assert.go delete mode 100644 assert/trace.go create mode 100644 builtins.go create mode 100644 bytecode.go create mode 100644 bytecode_test.go delete mode 100644 cli/cli.go delete mode 100644 cli/cli_test.go delete mode 100644 cmd/tengomin/main.go create mode 100644 compiler.go delete mode 100644 compiler/ast/array_lit.go delete mode 100644 compiler/ast/assign_stmt.go delete mode 100644 compiler/ast/ast.go delete mode 100644 compiler/ast/bad_expr.go delete mode 100644 compiler/ast/bad_stmt.go delete mode 100644 compiler/ast/binary_expr.go delete mode 100644 compiler/ast/block_stmt.go delete mode 100644 compiler/ast/bool_lit.go delete mode 100644 compiler/ast/branch_stmt.go delete mode 100644 compiler/ast/call_expr.go delete mode 100644 compiler/ast/char_lit.go delete mode 100644 compiler/ast/cond_expr.go delete mode 100644 compiler/ast/empty_stmt.go delete mode 100644 compiler/ast/error_expr.go delete mode 100644 compiler/ast/export_stmt.go delete mode 100644 compiler/ast/expr.go delete mode 100644 compiler/ast/expr_stmt.go delete mode 100644 compiler/ast/float_lit.go delete mode 100644 compiler/ast/for_in_stmt.go delete mode 100644 compiler/ast/for_stmt.go delete mode 100644 compiler/ast/func_lit.go delete mode 100644 compiler/ast/func_type.go delete mode 100644 compiler/ast/ident.go delete mode 100644 compiler/ast/if_stmt.go delete mode 100644 compiler/ast/immutable_expr.go delete mode 100644 compiler/ast/import_expr.go delete mode 100644 compiler/ast/inc_dec_stmt.go delete mode 100644 compiler/ast/index_expr.go delete mode 100644 compiler/ast/int_lit.go delete mode 100644 compiler/ast/map_element_lit.go delete mode 100644 compiler/ast/map_lit.go delete mode 100644 compiler/ast/node.go delete mode 100644 compiler/ast/paren_expr.go delete mode 100644 compiler/ast/return_stmt.go delete mode 100644 compiler/ast/selector_expr.go delete mode 100644 compiler/ast/slice_expr.go delete mode 100644 compiler/ast/stmt.go delete mode 100644 compiler/ast/string_lit.go delete mode 100644 compiler/ast/unary_expr.go delete mode 100644 compiler/ast/undefined_lit.go delete mode 100644 compiler/bytecode.go delete mode 100644 compiler/bytecode_decode.go delete mode 100644 compiler/bytecode_optimize.go delete mode 100644 compiler/bytecode_test.go delete mode 100644 compiler/compilation_scope.go delete mode 100644 compiler/compiler.go delete mode 100644 compiler/compiler_assign.go delete mode 100644 compiler/compiler_error_report_test.go delete mode 100644 compiler/compiler_for.go delete mode 100644 compiler/compiler_logical.go delete mode 100644 compiler/compiler_loops.go delete mode 100644 compiler/compiler_module.go delete mode 100644 compiler/compiler_optimize_test.go delete mode 100644 compiler/compiler_scopes.go delete mode 100644 compiler/compiler_scopes_test.go delete mode 100644 compiler/compiler_test.go delete mode 100644 compiler/emitted_instruction.go delete mode 100644 compiler/error.go delete mode 100644 compiler/instructions_test.go delete mode 100644 compiler/loop.go delete mode 100644 compiler/module_loader.go delete mode 100644 compiler/opcodes_test.go delete mode 100644 compiler/parser/error.go delete mode 100644 compiler/parser/error_list.go delete mode 100644 compiler/parser/error_list_test.go delete mode 100644 compiler/parser/error_test.go delete mode 100644 compiler/parser/parse_source.go delete mode 100644 compiler/parser/parser_array_test.go delete mode 100644 compiler/parser/parser_assignment_test.go delete mode 100644 compiler/parser/parser_boolean_test.go delete mode 100644 compiler/parser/parser_call_test.go delete mode 100644 compiler/parser/parser_char_test.go delete mode 100644 compiler/parser/parser_cond_test.go delete mode 100644 compiler/parser/parser_error_test.go delete mode 100644 compiler/parser/parser_for_in_test.go delete mode 100644 compiler/parser/parser_for_test.go delete mode 100644 compiler/parser/parser_function_test.go delete mode 100644 compiler/parser/parser_if_test.go delete mode 100644 compiler/parser/parser_import_test.go delete mode 100644 compiler/parser/parser_index_test.go delete mode 100644 compiler/parser/parser_logical_test.go delete mode 100644 compiler/parser/parser_map_test.go delete mode 100644 compiler/parser/parser_precendence_test.go delete mode 100644 compiler/parser/parser_selector_test.go delete mode 100644 compiler/parser/parser_semicolon_test.go delete mode 100644 compiler/parser/parser_string_test.go delete mode 100644 compiler/parser/parser_test.go delete mode 100644 compiler/parser/sync.go delete mode 100644 compiler/scanner/error_handler.go delete mode 100644 compiler/scanner/mode.go delete mode 100644 compiler/source/file.go delete mode 100644 compiler/source/file_pos.go delete mode 100644 compiler/source/file_set.go delete mode 100644 compiler/symbol.go delete mode 100644 compiler/symbol_scopes.go delete mode 100644 compiler/symbol_table_test.go delete mode 100644 compiler/token/keywords.go create mode 100644 compiler_test.go create mode 100644 errors.go rename objects/formatter.go => formatter.go (91%) rename compiler/ast/ident_list.go => internal/ast.go (65%) rename compiler/ast/ident_list_test.go => internal/ast_test.go (52%) rename compiler/symbol_table.go => internal/compile.go (70%) create mode 100644 internal/expr.go rename {compiler/ast => internal}/file.go (62%) rename compiler/instructions.go => internal/internal.go (50%) rename {compiler => internal}/opcodes.go (95%) rename {compiler/parser => internal}/parser.go (66%) create mode 100644 internal/parser_test.go rename {compiler/source => internal}/pos.go (92%) create mode 100644 internal/require/require.go rename {compiler/scanner => internal}/scanner.go (83%) rename {compiler/scanner => internal}/scanner_test.go (80%) create mode 100644 internal/source_file.go create mode 100644 internal/stmt.go create mode 100644 internal/symbol_table_test.go rename {compiler => internal}/token/tokens.go (91%) create mode 100644 iterator.go rename objects/module_map.go => modules.go (63%) create mode 100644 objects.go delete mode 100644 objects/array.go delete mode 100644 objects/array_iterator.go delete mode 100644 objects/array_test.go delete mode 100644 objects/bool.go delete mode 100644 objects/builtin_append.go delete mode 100644 objects/builtin_convert.go delete mode 100644 objects/builtin_copy.go delete mode 100644 objects/builtin_format.go delete mode 100644 objects/builtin_function.go delete mode 100644 objects/builtin_len.go delete mode 100644 objects/builtin_module.go delete mode 100644 objects/builtin_type.go delete mode 100644 objects/builtin_type_checks.go delete mode 100644 objects/builtins.go delete mode 100644 objects/bytes.go delete mode 100644 objects/bytes_iterator.go delete mode 100644 objects/callable.go delete mode 100644 objects/callable_func.go delete mode 100644 objects/char.go delete mode 100644 objects/closure.go delete mode 100644 objects/compiled_function.go delete mode 100644 objects/conversion.go delete mode 100644 objects/count_objects.go delete mode 100644 objects/count_objects_test.go delete mode 100644 objects/error.go delete mode 100644 objects/error_test.go delete mode 100644 objects/errors.go delete mode 100644 objects/float.go delete mode 100644 objects/float_test.go delete mode 100644 objects/immutable_array.go delete mode 100644 objects/immutable_map.go delete mode 100644 objects/importable.go delete mode 100644 objects/index_assignable.go delete mode 100644 objects/indexable.go delete mode 100644 objects/int.go delete mode 100644 objects/int_test.go delete mode 100644 objects/iterable.go delete mode 100644 objects/iterator.go delete mode 100644 objects/map.go delete mode 100644 objects/map_iterator.go delete mode 100644 objects/map_test.go delete mode 100644 objects/object.go delete mode 100644 objects/object_ptr.go delete mode 100644 objects/object_test.go delete mode 100644 objects/objects.go delete mode 100644 objects/objects_test.go delete mode 100644 objects/source_module.go delete mode 100644 objects/string.go delete mode 100644 objects/string_iterator.go delete mode 100644 objects/string_test.go delete mode 100644 objects/time.go delete mode 100644 objects/undefined.go delete mode 100644 objects/user_function.go create mode 100644 objects_test.go delete mode 100644 runtime/errors.go delete mode 100644 runtime/frame.go delete mode 100644 runtime/vm_array_test.go delete mode 100644 runtime/vm_assignment_test.go delete mode 100644 runtime/vm_bitwise_test.go delete mode 100644 runtime/vm_boolean_test.go delete mode 100644 runtime/vm_builtin_test.go delete mode 100644 runtime/vm_bytes_test.go delete mode 100644 runtime/vm_call_test.go delete mode 100644 runtime/vm_char_test.go delete mode 100644 runtime/vm_cond_test.go delete mode 100644 runtime/vm_equality_test.go delete mode 100644 runtime/vm_error_report_test.go delete mode 100644 runtime/vm_error_test.go delete mode 100644 runtime/vm_float_test.go delete mode 100644 runtime/vm_for_in_test.go delete mode 100644 runtime/vm_for_test.go delete mode 100644 runtime/vm_function_test.go delete mode 100644 runtime/vm_if_test.go delete mode 100644 runtime/vm_immutable_test.go delete mode 100644 runtime/vm_inc_dec_test.go delete mode 100644 runtime/vm_indexable_test.go delete mode 100644 runtime/vm_integer_test.go delete mode 100644 runtime/vm_iterable_test.go delete mode 100644 runtime/vm_logic_test.go delete mode 100644 runtime/vm_map_test.go delete mode 100644 runtime/vm_module_test.go delete mode 100644 runtime/vm_not_operator_test.go delete mode 100644 runtime/vm_objects_limit_test.go delete mode 100644 runtime/vm_return_test.go delete mode 100644 runtime/vm_scopes_test.go delete mode 100644 runtime/vm_selector_test.go delete mode 100644 runtime/vm_source_modules_test.go delete mode 100644 runtime/vm_srcmod_test.go delete mode 100644 runtime/vm_stack_overflow_test.go delete mode 100644 runtime/vm_string_test.go delete mode 100644 runtime/vm_tail_call_test.go delete mode 100644 runtime/vm_test.go delete mode 100644 runtime/vm_undefined_test.go create mode 100644 script.go delete mode 100644 script/compiled.go delete mode 100644 script/compiled_test.go delete mode 100644 script/script.go delete mode 100644 script/script_concurrency_test.go delete mode 100644 script/script_custom_test.go delete mode 100644 script/script_module_test.go delete mode 100644 script/script_test.go delete mode 100644 script/variable_test.go create mode 100644 script_test.go create mode 100644 tengo_test.go rename script/variable.go => variable.go (51%) create mode 100644 variable_test.go rename runtime/vm.go => vm.go (56%) create mode 100644 vm_test.go diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..8c8171f --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,47 @@ +version: 2 +jobs: + release: + docker: + - image: circleci/golang:1.13 + working_directory: /go/src/github.com/d5/tegno + steps: + - checkout + - restore_cache: + keys: + - go-mod-v1-{{ checksum "go.sum" }} + - run: curl -sL https://git.io/goreleaser | bash + - save_cache: + key: go-mod-v1-{{ checksum "go.sum" }} + paths: + - "/go/pkg/mod" + test: + docker: + - image: circleci/golang:1.13 + working_directory: /go/src/github.com/d5/tegno + steps: + - checkout + - restore_cache: + keys: + - go-mod-v1-{{ checksum "go.sum" }} + - run: go get -u golang.org/x/lint/golint + - run: make test + - save_cache: + key: go-mod-v1-{{ checksum "go.sum" }} + paths: + - "/go/pkg/mod" +workflows: + version: 2 + test: + jobs: + - test: + filters: + tags: + ignore: /.*/ + release: + jobs: + - release: + filters: + branches: + ignore: /.*/ + tags: + only: /v[0-9]+(\.[0-9]+)*(-.*)*/ diff --git a/.goreleaser.yml b/.goreleaser.yml index 9b1dffe..1bd1432 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -11,15 +11,6 @@ builds: - darwin - linux - windows - - env: - - CGO_ENABLED=0 - main: ./cmd/tengomin/main.go - id: tengomin - binary: tengomin - goos: - - darwin - - linux - - windows archive: files: - none* diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 57986ae..0000000 --- a/.travis.yml +++ /dev/null @@ -1,17 +0,0 @@ -language: go - -go: - - "1.12" - -install: - - env GO111MODULE=on go get -u golang.org/x/lint/golint - -script: - - env GO111MODULE=on make test - -deploy: - - provider: script - skip_cleanup: true - script: curl -sL https://git.io/goreleaser | bash - on: - tags: true \ No newline at end of file diff --git a/Makefile b/Makefile index 99daafd..793bc12 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,10 @@ -vet: - go vet ./... - generate: go generate ./... lint: golint -set_exit_status ./... -test: generate vet lint +test: generate lint go test -race -cover ./... fmt: diff --git a/README.md b/README.md index af8bd84..a20f376 100644 --- a/README.md +++ b/README.md @@ -4,18 +4,18 @@ # The Tengo Language -[![GoDoc](https://godoc.org/github.com/d5/tengo?status.svg)](https://godoc.org/github.com/d5/tengo/script) +[![GoDoc](https://godoc.org/github.com/d5/tengo?status.svg)](https://godoc.org/github.com/d5/tengo) [![Go Report Card](https://goreportcard.com/badge/github.com/d5/tengo)](https://goreportcard.com/report/github.com/d5/tengo) -[![Build Status](https://travis-ci.org/d5/tengo.svg?branch=master)](https://travis-ci.org/d5/tengo) +[![CircleCI](https://circleci.com/gh/d5/tengo.svg?style=svg)](https://circleci.com/gh/d5/tengo) [![Sourcegraph](https://sourcegraph.com/github.com/d5/tengo/-/badge.svg)](https://sourcegraph.com/github.com/d5/tengo?badge) **Tengo is a small, dynamic, fast, secure script language for Go.** -Tengo is **[fast](#benchmark)** and secure because it's compiled/executed as bytecode on stack-based VM that's written in native Go. +Tengo is **[fast](#benchmark)** and secure because it's compiled/executed as +bytecode on stack-based VM that's written in native Go. ```golang /* The Tengo Language */ - fmt := import("fmt") each := func(seq, fn) { @@ -31,19 +31,24 @@ fmt.println(sum(0, [1, 2, 3])) // "6" fmt.println(sum("", [1, 2, 3])) // "123" ``` -> Run this code in the [Playground](https://tengolang.com/?s=0c8d5d0d88f2795a7093d7f35ae12c3afa17bea3) +> Test this Tengo code in the +> [Tengo Playground](https://tengolang.com/?s=0c8d5d0d88f2795a7093d7f35ae12c3afa17bea3) ## Features -- Simple and highly readable [Syntax](https://github.com/d5/tengo/blob/master/docs/tutorial.md) +- Simple and highly readable + [Syntax](https://github.com/d5/tengo/blob/master/docs/tutorial.md) - Dynamic typing with type coercion - Higher-order functions and closures - Immutable values - - Garbage collection -- [Securely Embeddable](https://github.com/d5/tengo/blob/master/docs/interoperability.md) and [Extensible](https://github.com/d5/tengo/blob/master/docs/objects.md) +- [Securely Embeddable](https://github.com/d5/tengo/blob/master/docs/interoperability.md) + and [Extensible](https://github.com/d5/tengo/blob/master/docs/objects.md) - Compiler/runtime written in native Go _(no external deps or cgo)_ -- Executable as a [standalone](https://github.com/d5/tengo/blob/master/docs/tengo-cli.md) language / REPL -- Use cases: rules engine, [state machine](https://github.com/d5/go-fsm), [gaming](https://github.com/d5/pbr), data pipeline, [transpiler](https://github.com/d5/tengo2lua) +- Executable as a + [standalone](https://github.com/d5/tengo/blob/master/docs/tengo-cli.md) + language / REPL +- Use cases: rules engine, [state machine](https://github.com/d5/go-fsm), + data pipeline, [transpiler](https://github.com/d5/tengo2lua) ## Benchmark @@ -61,16 +66,70 @@ fmt.println(sum("", [1, 2, 3])) // "123" | [otto](https://github.com/robertkrimen/otto) | `68,377ms` | `11ms` | JS Interpreter on Go | | [Anko](https://github.com/mattn/anko) | `92,579ms` | `18ms` | Interpreter on Go | -_* [fib(35)](https://github.com/d5/tengobench/blob/master/code/fib.tengo): Fibonacci(35)_ -_* [fibt(35)](https://github.com/d5/tengobench/blob/master/code/fibtc.tengo): [tail-call](https://en.wikipedia.org/wiki/Tail_call) version of Fibonacci(35)_ +_* [fib(35)](https://github.com/d5/tengobench/blob/master/code/fib.tengo): +Fibonacci(35)_ +_* [fibt(35)](https://github.com/d5/tengobench/blob/master/code/fibtc.tengo): +[tail-call](https://en.wikipedia.org/wiki/Tail_call) version of Fibonacci(35)_ _* **Go** does not read the source code from file, while all other cases do_ _* See [here](https://github.com/d5/tengobench) for commands/codes used_ +## Quick Start + +A simple Go example code that compiles/runs Tengo script code with some input/output values: + +```golang +package main + +import ( + "context" + "fmt" + + "github.com/d5/tengo" +) + +func main() { + // Tengo script code + src := ` +each := func(seq, fn) { + for x in seq { fn(x) } +} + +sum := 0 +mul := 1 +each([a, b, c, d], func(x) { + sum += x + mul *= x +})` + + // create a new Script instance + script := tengo.NewScript([]byte(src)) + + // set values + _ = script.Add("a", 1) + _ = script.Add("b", 9) + _ = script.Add("c", 8) + _ = script.Add("d", 4) + + // run the script + compiled, err := script.RunContext(context.Background()) + if err != nil { + panic(err) + } + + // retrieve values + sum := compiled.Get("sum") + mul := compiled.Get("mul") + fmt.Println(sum, mul) // "22 288" +} + +``` + ## References - [Language Syntax](https://github.com/d5/tengo/blob/master/docs/tutorial.md) - [Object Types](https://github.com/d5/tengo/blob/master/docs/objects.md) -- [Runtime Types](https://github.com/d5/tengo/blob/master/docs/runtime-types.md) and [Operators](https://github.com/d5/tengo/blob/master/docs/operators.md) +- [Runtime Types](https://github.com/d5/tengo/blob/master/docs/runtime-types.md) + and [Operators](https://github.com/d5/tengo/blob/master/docs/operators.md) - [Builtin Functions](https://github.com/d5/tengo/blob/master/docs/builtins.md) - [Interoperability](https://github.com/d5/tengo/blob/master/docs/interoperability.md) - [Tengo CLI](https://github.com/d5/tengo/blob/master/docs/tengo-cli.md) diff --git a/assert/assert.go b/assert/assert.go deleted file mode 100644 index 84d1aaa..0000000 --- a/assert/assert.go +++ /dev/null @@ -1,356 +0,0 @@ -package assert - -import ( - "bytes" - "fmt" - "reflect" - "strings" - "testing" - - "github.com/d5/tengo/compiler" - "github.com/d5/tengo/compiler/source" - "github.com/d5/tengo/compiler/token" - "github.com/d5/tengo/objects" -) - -// NoError asserts err is not an error. -func NoError(t *testing.T, err error, msg ...interface{}) bool { - if err == nil { - return true - } - - return failExpectedActual(t, "no error", err, msg...) -} - -// Error asserts err is an error. -func Error(t *testing.T, err error, msg ...interface{}) bool { - if err != nil { - return true - } - - return failExpectedActual(t, "error", err, msg...) -} - -// Nil asserts v is nil. -func Nil(t *testing.T, v interface{}, msg ...interface{}) bool { - if isNil(v) { - return true - } - - return failExpectedActual(t, "nil", v, msg...) -} - -// True asserts v is true. -func True(t *testing.T, v bool, msg ...interface{}) bool { - if v { - return true - } - - return failExpectedActual(t, "true", v, msg...) -} - -// False asserts vis false. -func False(t *testing.T, v bool, msg ...interface{}) bool { - if !v { - return true - } - - return failExpectedActual(t, "false", v, msg...) -} - -// NotNil asserts v is not nil. -func NotNil(t *testing.T, v interface{}, msg ...interface{}) bool { - if !isNil(v) { - return true - } - - return failExpectedActual(t, "not nil", v, msg...) -} - -// IsType asserts expected and actual are of the same type. -func IsType(t *testing.T, expected, actual interface{}, msg ...interface{}) bool { - if reflect.TypeOf(expected) == reflect.TypeOf(actual) { - return true - } - - return failExpectedActual(t, reflect.TypeOf(expected), reflect.TypeOf(actual), msg...) -} - -// Equal asserts expected and actual are equal. -func Equal(t *testing.T, expected, actual interface{}, msg ...interface{}) bool { - if isNil(expected) { - return Nil(t, actual, "expected nil, but got not nil") - } - if !NotNil(t, actual, "expected not nil, but got nil") { - return false - } - if !IsType(t, expected, actual, msg...) { - return false - } - - switch expected := expected.(type) { - case int: - if expected != actual.(int) { - return failExpectedActual(t, expected, actual, msg...) - } - case int64: - if expected != actual.(int64) { - return failExpectedActual(t, expected, actual, msg...) - } - case float64: - if expected != actual.(float64) { - return failExpectedActual(t, expected, actual, msg...) - } - case string: - if expected != actual.(string) { - return failExpectedActual(t, expected, actual, msg...) - } - case []byte: - if !bytes.Equal(expected, actual.([]byte)) { - return failExpectedActual(t, string(expected), string(actual.([]byte)), msg...) - } - case []string: - if !equalStringSlice(expected, actual.([]string)) { - return failExpectedActual(t, expected, actual, msg...) - } - case []int: - if !equalIntSlice(expected, actual.([]int)) { - return failExpectedActual(t, expected, actual, msg...) - } - case bool: - if expected != actual.(bool) { - return failExpectedActual(t, expected, actual, msg...) - } - case rune: - if expected != actual.(rune) { - return failExpectedActual(t, expected, actual, msg...) - } - case *compiler.Symbol: - if !equalSymbol(expected, actual.(*compiler.Symbol)) { - return failExpectedActual(t, expected, actual, msg...) - } - case source.Pos: - if expected != actual.(source.Pos) { - return failExpectedActual(t, expected, actual, msg...) - } - case token.Token: - if expected != actual.(token.Token) { - return failExpectedActual(t, expected, actual, msg...) - } - case []objects.Object: - return equalObjectSlice(t, expected, actual.([]objects.Object), msg...) - case *objects.Int: - return Equal(t, expected.Value, actual.(*objects.Int).Value, msg...) - case *objects.Float: - return Equal(t, expected.Value, actual.(*objects.Float).Value, msg...) - case *objects.String: - return Equal(t, expected.Value, actual.(*objects.String).Value, msg...) - case *objects.Char: - return Equal(t, expected.Value, actual.(*objects.Char).Value, msg...) - case *objects.Bool: - if expected != actual { - return failExpectedActual(t, expected, actual, msg...) - } - case *objects.Array: - return equalObjectSlice(t, expected.Value, actual.(*objects.Array).Value, msg...) - case *objects.ImmutableArray: - return equalObjectSlice(t, expected.Value, actual.(*objects.ImmutableArray).Value, msg...) - case *objects.Bytes: - if !bytes.Equal(expected.Value, actual.(*objects.Bytes).Value) { - return failExpectedActual(t, string(expected.Value), string(actual.(*objects.Bytes).Value), msg...) - } - case *objects.Map: - return equalObjectMap(t, expected.Value, actual.(*objects.Map).Value, msg...) - case *objects.ImmutableMap: - return equalObjectMap(t, expected.Value, actual.(*objects.ImmutableMap).Value, msg...) - case *objects.CompiledFunction: - return equalCompiledFunction(t, expected, actual.(*objects.CompiledFunction), msg...) - case *objects.Closure: - return equalClosure(t, expected, actual.(*objects.Closure), msg...) - case *objects.Undefined: - if expected != actual { - return failExpectedActual(t, expected, actual, msg...) - } - case *objects.Error: - return Equal(t, expected.Value, actual.(*objects.Error).Value, msg...) - case objects.Object: - if !expected.Equals(actual.(objects.Object)) { - return failExpectedActual(t, expected, actual, msg...) - } - case *source.FileSet: - return equalFileSet(t, expected, actual.(*source.FileSet), msg...) - case *source.File: - return Equal(t, expected.Name, actual.(*source.File).Name, msg...) && - Equal(t, expected.Base, actual.(*source.File).Base, msg...) && - Equal(t, expected.Size, actual.(*source.File).Size, msg...) && - True(t, equalIntSlice(expected.Lines, actual.(*source.File).Lines), msg...) - case error: - if expected != actual.(error) { - return failExpectedActual(t, expected, actual, msg...) - } - default: - panic(fmt.Errorf("type not implemented: %T", expected)) - } - - return true -} - -// Fail marks the function as having failed but continues execution. -func Fail(t *testing.T, msg ...interface{}) bool { - t.Logf("\nError trace:\n\t%s\n%s", strings.Join(errorTrace(), "\n\t"), message(msg...)) - - t.Fail() - - return false -} - -func failExpectedActual(t *testing.T, expected, actual interface{}, msg ...interface{}) bool { - var addMsg string - if len(msg) > 0 { - addMsg = "\nMessage: " + message(msg...) - } - - t.Logf("\nError trace:\n\t%s\nExpected: %v\nActual: %v%s", - strings.Join(errorTrace(), "\n\t"), - expected, actual, - addMsg) - - t.Fail() - - return false -} - -func message(formatArgs ...interface{}) string { - var format string - var args []interface{} - if len(formatArgs) > 0 { - format = formatArgs[0].(string) - } - if len(formatArgs) > 1 { - args = formatArgs[1:] - } - - return fmt.Sprintf(format, args...) -} - -func equalIntSlice(a, b []int) bool { - if len(a) != len(b) { - return false - } - - for i := 0; i < len(a); i++ { - if a[i] != b[i] { - return false - } - } - - return true -} - -func equalStringSlice(a, b []string) bool { - if len(a) != len(b) { - return false - } - - for i := 0; i < len(a); i++ { - if a[i] != b[i] { - return false - } - } - - return true -} - -func equalSymbol(a, b *compiler.Symbol) bool { - return a.Name == b.Name && - a.Index == b.Index && - a.Scope == b.Scope -} - -func equalObjectSlice(t *testing.T, expected, actual []objects.Object, msg ...interface{}) bool { - if !Equal(t, len(expected), len(actual), msg...) { - return false - } - - for i := 0; i < len(expected); i++ { - if !Equal(t, expected[i], actual[i], msg...) { - return false - } - } - - return true -} - -func equalFileSet(t *testing.T, expected, actual *source.FileSet, msg ...interface{}) bool { - if !Equal(t, len(expected.Files), len(actual.Files), msg...) { - return false - } - for i, f := range expected.Files { - if !Equal(t, f, actual.Files[i], msg...) { - return false - } - } - - return Equal(t, expected.Base, actual.Base) && - Equal(t, expected.LastFile, actual.LastFile) -} - -func equalObjectMap(t *testing.T, expected, actual map[string]objects.Object, msg ...interface{}) bool { - if !Equal(t, len(expected), len(actual), msg...) { - return false - } - - for key, expectedVal := range expected { - actualVal := actual[key] - - if !Equal(t, expectedVal, actualVal, msg...) { - return false - } - } - - return true -} - -func equalCompiledFunction(t *testing.T, expected, actual objects.Object, msg ...interface{}) bool { - expectedT := expected.(*objects.CompiledFunction) - actualT := actual.(*objects.CompiledFunction) - - return Equal(t, - compiler.FormatInstructions(expectedT.Instructions, 0), - compiler.FormatInstructions(actualT.Instructions, 0), msg...) -} - -func equalClosure(t *testing.T, expected, actual objects.Object, msg ...interface{}) bool { - expectedT := expected.(*objects.Closure) - actualT := actual.(*objects.Closure) - - if !Equal(t, expectedT.Fn, actualT.Fn, msg...) { - return false - } - - if !Equal(t, len(expectedT.Free), len(actualT.Free), msg...) { - return false - } - - for i := 0; i < len(expectedT.Free); i++ { - if !Equal(t, *expectedT.Free[i], *actualT.Free[i], msg...) { - return false - } - } - - return true -} - -func isNil(v interface{}) bool { - if v == nil { - return true - } - - value := reflect.ValueOf(v) - kind := value.Kind() - if kind >= reflect.Chan && kind <= reflect.Slice && value.IsNil() { - return true - } - - return false -} diff --git a/assert/trace.go b/assert/trace.go deleted file mode 100644 index 5ba1390..0000000 --- a/assert/trace.go +++ /dev/null @@ -1,72 +0,0 @@ -package assert - -import ( - "fmt" - "runtime" - "strings" - "unicode" - "unicode/utf8" -) - -func errorTrace() []string { - var pc uintptr - file := "" - line := 0 - var ok bool - name := "" - - var callers []string - for i := 0; ; i++ { - pc, file, line, ok = runtime.Caller(i) - if !ok { - break - } - - if file == "" { - break - } - - f := runtime.FuncForPC(pc) - if f == nil { - break - } - name = f.Name() - - if name == "testing.tRunner" { - break - } - - parts := strings.Split(file, "/") - file = parts[len(parts)-1] - if len(parts) > 1 { - dir := parts[len(parts)-2] - if (dir != "assert" && dir != "mock" && dir != "require") || file == "mock_test.go" { - callers = append(callers, fmt.Sprintf("%s:%d", file, line)) - } - } - - // Drop the package - segments := strings.Split(name, ".") - name = segments[len(segments)-1] - if isTest(name, "Test") || - isTest(name, "Benchmark") || - isTest(name, "Example") { - break - } - } - - return callers -} - -func isTest(name, prefix string) bool { - if !strings.HasPrefix(name, prefix) { - return false - } - if len(name) == len(prefix) { // "Test" is ok - return true - } - - r, _ := utf8.DecodeRuneInString(name[len(prefix):]) - - return !unicode.IsLower(r) -} diff --git a/builtins.go b/builtins.go new file mode 100644 index 0000000..87f7fc3 --- /dev/null +++ b/builtins.go @@ -0,0 +1,502 @@ +package tengo + +var builtinFuncs = []*BuiltinFunction{ + { + Name: "len", + Value: builtinLen, + }, + { + Name: "copy", + Value: builtinCopy, + }, + { + Name: "append", + Value: builtinAppend, + }, + { + Name: "string", + Value: builtinString, + }, + { + Name: "int", + Value: builtinInt, + }, + { + Name: "bool", + Value: builtinBool, + }, + { + Name: "float", + Value: builtinFloat, + }, + { + Name: "char", + Value: builtinChar, + }, + { + Name: "bytes", + Value: builtinBytes, + }, + { + Name: "time", + Value: builtinTime, + }, + { + Name: "is_int", + Value: builtinIsInt, + }, + { + Name: "is_float", + Value: builtinIsFloat, + }, + { + Name: "is_string", + Value: builtinIsString, + }, + { + Name: "is_bool", + Value: builtinIsBool, + }, + { + Name: "is_char", + Value: builtinIsChar, + }, + { + Name: "is_bytes", + Value: builtinIsBytes, + }, + { + Name: "is_array", + Value: builtinIsArray, + }, + { + Name: "is_immutable_array", + Value: builtinIsImmutableArray, + }, + { + Name: "is_map", + Value: builtinIsMap, + }, + { + Name: "is_immutable_map", + Value: builtinIsImmutableMap, + }, + { + Name: "is_iterable", + Value: builtinIsIterable, + }, + { + Name: "is_time", + Value: builtinIsTime, + }, + { + Name: "is_error", + Value: builtinIsError, + }, + { + Name: "is_undefined", + Value: builtinIsUndefined, + }, + { + Name: "is_function", + Value: builtinIsFunction, + }, + { + Name: "is_callable", + Value: builtinIsCallable, + }, + { + Name: "type_name", + Value: builtinTypeName, + }, + { + Name: "format", + Value: builtinFormat, + }, +} + +// GetAllBuiltinFunctions returns all builtin function objects. +func GetAllBuiltinFunctions() []*BuiltinFunction { + return append([]*BuiltinFunction{}, builtinFuncs...) +} + +func builtinTypeName(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + return &String{Value: args[0].TypeName()}, nil +} + +func builtinIsString(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + if _, ok := args[0].(*String); ok { + return TrueValue, nil + } + return FalseValue, nil +} + +func builtinIsInt(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + if _, ok := args[0].(*Int); ok { + return TrueValue, nil + } + return FalseValue, nil +} + +func builtinIsFloat(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + if _, ok := args[0].(*Float); ok { + return TrueValue, nil + } + return FalseValue, nil +} + +func builtinIsBool(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + if _, ok := args[0].(*Bool); ok { + return TrueValue, nil + } + return FalseValue, nil +} + +func builtinIsChar(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + if _, ok := args[0].(*Char); ok { + return TrueValue, nil + } + return FalseValue, nil +} + +func builtinIsBytes(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + if _, ok := args[0].(*Bytes); ok { + return TrueValue, nil + } + return FalseValue, nil +} + +func builtinIsArray(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + if _, ok := args[0].(*Array); ok { + return TrueValue, nil + } + return FalseValue, nil +} + +func builtinIsImmutableArray(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + if _, ok := args[0].(*ImmutableArray); ok { + return TrueValue, nil + } + return FalseValue, nil +} + +func builtinIsMap(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + if _, ok := args[0].(*Map); ok { + return TrueValue, nil + } + return FalseValue, nil +} + +func builtinIsImmutableMap(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + if _, ok := args[0].(*ImmutableMap); ok { + return TrueValue, nil + } + return FalseValue, nil +} + +func builtinIsTime(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + if _, ok := args[0].(*Time); ok { + return TrueValue, nil + } + return FalseValue, nil +} + +func builtinIsError(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + if _, ok := args[0].(*Error); ok { + return TrueValue, nil + } + return FalseValue, nil +} + +func builtinIsUndefined(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + if args[0] == UndefinedValue { + return TrueValue, nil + } + return FalseValue, nil +} + +func builtinIsFunction(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + switch args[0].(type) { + case *CompiledFunction: + return TrueValue, nil + } + return FalseValue, nil +} + +func builtinIsCallable(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + if args[0].CanCall() { + return TrueValue, nil + } + return FalseValue, nil +} + +func builtinIsIterable(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + if args[0].CanIterate() { + return TrueValue, nil + } + return FalseValue, nil +} + +// len(obj object) => int +func builtinLen(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + switch arg := args[0].(type) { + case *Array: + return &Int{Value: int64(len(arg.Value))}, nil + case *ImmutableArray: + return &Int{Value: int64(len(arg.Value))}, nil + case *String: + return &Int{Value: int64(len(arg.Value))}, nil + case *Bytes: + return &Int{Value: int64(len(arg.Value))}, nil + case *Map: + return &Int{Value: int64(len(arg.Value))}, nil + case *ImmutableMap: + return &Int{Value: int64(len(arg.Value))}, nil + default: + return nil, ErrInvalidArgumentType{ + Name: "first", + Expected: "array/string/bytes/map", + Found: arg.TypeName(), + } + } +} + +func builtinFormat(args ...Object) (Object, error) { + numArgs := len(args) + if numArgs == 0 { + return nil, ErrWrongNumArguments + } + format, ok := args[0].(*String) + if !ok { + return nil, ErrInvalidArgumentType{ + Name: "format", + Expected: "string", + Found: args[0].TypeName(), + } + } + if numArgs == 1 { + // okay to return 'format' directly as String is immutable + return format, nil + } + s, err := Format(format.Value, args[1:]...) + if err != nil { + return nil, err + } + return &String{Value: s}, nil +} + +func builtinCopy(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + return args[0].Copy(), nil +} + +func builtinString(args ...Object) (Object, error) { + argsLen := len(args) + if !(argsLen == 1 || argsLen == 2) { + return nil, ErrWrongNumArguments + } + if _, ok := args[0].(*String); ok { + return args[0], nil + } + v, ok := ToString(args[0]) + if ok { + if len(v) > MaxStringLen { + return nil, ErrStringLimit + } + return &String{Value: v}, nil + } + if argsLen == 2 { + return args[1], nil + } + return UndefinedValue, nil +} + +func builtinInt(args ...Object) (Object, error) { + argsLen := len(args) + if !(argsLen == 1 || argsLen == 2) { + return nil, ErrWrongNumArguments + } + if _, ok := args[0].(*Int); ok { + return args[0], nil + } + v, ok := ToInt64(args[0]) + if ok { + return &Int{Value: v}, nil + } + if argsLen == 2 { + return args[1], nil + } + return UndefinedValue, nil +} + +func builtinFloat(args ...Object) (Object, error) { + argsLen := len(args) + if !(argsLen == 1 || argsLen == 2) { + return nil, ErrWrongNumArguments + } + if _, ok := args[0].(*Float); ok { + return args[0], nil + } + v, ok := ToFloat64(args[0]) + if ok { + return &Float{Value: v}, nil + } + if argsLen == 2 { + return args[1], nil + } + return UndefinedValue, nil +} + +func builtinBool(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + if _, ok := args[0].(*Bool); ok { + return args[0], nil + } + v, ok := ToBool(args[0]) + if ok { + if v { + return TrueValue, nil + } + return FalseValue, nil + } + return UndefinedValue, nil +} + +func builtinChar(args ...Object) (Object, error) { + argsLen := len(args) + if !(argsLen == 1 || argsLen == 2) { + return nil, ErrWrongNumArguments + } + if _, ok := args[0].(*Char); ok { + return args[0], nil + } + v, ok := ToRune(args[0]) + if ok { + return &Char{Value: v}, nil + } + if argsLen == 2 { + return args[1], nil + } + return UndefinedValue, nil +} + +func builtinBytes(args ...Object) (Object, error) { + argsLen := len(args) + if !(argsLen == 1 || argsLen == 2) { + return nil, ErrWrongNumArguments + } + + // bytes(N) => create a new bytes with given size N + if n, ok := args[0].(*Int); ok { + if n.Value > int64(MaxBytesLen) { + return nil, ErrBytesLimit + } + return &Bytes{Value: make([]byte, int(n.Value))}, nil + } + v, ok := ToByteSlice(args[0]) + if ok { + if len(v) > MaxBytesLen { + return nil, ErrBytesLimit + } + return &Bytes{Value: v}, nil + } + if argsLen == 2 { + return args[1], nil + } + return UndefinedValue, nil +} + +func builtinTime(args ...Object) (Object, error) { + argsLen := len(args) + if !(argsLen == 1 || argsLen == 2) { + return nil, ErrWrongNumArguments + } + if _, ok := args[0].(*Time); ok { + return args[0], nil + } + v, ok := ToTime(args[0]) + if ok { + return &Time{Value: v}, nil + } + if argsLen == 2 { + return args[1], nil + } + return UndefinedValue, nil +} + +// append(arr, items...) +func builtinAppend(args ...Object) (Object, error) { + if len(args) < 2 { + return nil, ErrWrongNumArguments + } + switch arg := args[0].(type) { + case *Array: + return &Array{Value: append(arg.Value, args[1:]...)}, nil + case *ImmutableArray: + return &Array{Value: append(arg.Value, args[1:]...)}, nil + default: + return nil, ErrInvalidArgumentType{ + Name: "first", + Expected: "array", + Found: arg.TypeName(), + } + } +} diff --git a/bytecode.go b/bytecode.go new file mode 100644 index 0000000..c465693 --- /dev/null +++ b/bytecode.go @@ -0,0 +1,292 @@ +package tengo + +import ( + "encoding/gob" + "fmt" + "io" + "reflect" + + "github.com/d5/tengo/internal" +) + +// Bytecode is a compiled instructions and constants. +type Bytecode struct { + FileSet *internal.SourceFileSet + MainFunction *CompiledFunction + Constants []Object +} + +// Encode writes Bytecode data to the writer. +func (b *Bytecode) Encode(w io.Writer) error { + enc := gob.NewEncoder(w) + if err := enc.Encode(b.FileSet); err != nil { + return err + } + if err := enc.Encode(b.MainFunction); err != nil { + return err + } + return enc.Encode(b.Constants) +} + +// CountObjects returns the number of objects found in Constants. +func (b *Bytecode) CountObjects() int { + n := 0 + for _, c := range b.Constants { + n += CountObjects(c) + } + return n +} + +// FormatInstructions returns human readable string representations of +// compiled instructions. +func (b *Bytecode) FormatInstructions() []string { + return internal.FormatInstructions(b.MainFunction.Instructions, 0) +} + +// FormatConstants returns human readable string representations of +// compiled constants. +func (b *Bytecode) FormatConstants() (output []string) { + for cidx, cn := range b.Constants { + switch cn := cn.(type) { + case *CompiledFunction: + output = append(output, fmt.Sprintf( + "[% 3d] (Compiled Function|%p)", cidx, &cn)) + for _, l := range internal.FormatInstructions(cn.Instructions, 0) { + output = append(output, fmt.Sprintf(" %s", l)) + } + default: + output = append(output, fmt.Sprintf("[% 3d] %s (%s|%p)", + cidx, cn, reflect.TypeOf(cn).Elem().Name(), &cn)) + } + } + return +} + +// Decode reads Bytecode data from the reader. +func (b *Bytecode) Decode(r io.Reader, modules *ModuleMap) error { + if modules == nil { + modules = NewModuleMap() + } + + dec := gob.NewDecoder(r) + if err := dec.Decode(&b.FileSet); err != nil { + return err + } + // TODO: files in b.FileSet.File does not have their 'set' field properly + // set to b.FileSet as it's private field and not serialized by gob + // encoder/decoder. + if err := dec.Decode(&b.MainFunction); err != nil { + return err + } + if err := dec.Decode(&b.Constants); err != nil { + return err + } + for i, v := range b.Constants { + fv, err := fixDecodedObject(v, modules) + if err != nil { + return err + } + b.Constants[i] = fv + } + return nil +} + +// RemoveDuplicates finds and remove the duplicate values in Constants. +// Note this function mutates Bytecode. +func (b *Bytecode) RemoveDuplicates() { + var deduped []Object + + indexMap := make(map[int]int) // mapping from old constant index to new index + ints := make(map[int64]int) + strings := make(map[string]int) + floats := make(map[float64]int) + chars := make(map[rune]int) + immutableMaps := make(map[string]int) // for modules + + for curIdx, c := range b.Constants { + switch c := c.(type) { + case *CompiledFunction: + // add to deduped list + indexMap[curIdx] = len(deduped) + deduped = append(deduped, c) + case *ImmutableMap: + modName := inferModuleName(c) + newIdx, ok := immutableMaps[modName] + if modName != "" && ok { + indexMap[curIdx] = newIdx + } else { + newIdx = len(deduped) + immutableMaps[modName] = newIdx + indexMap[curIdx] = newIdx + deduped = append(deduped, c) + } + case *Int: + if newIdx, ok := ints[c.Value]; ok { + indexMap[curIdx] = newIdx + } else { + newIdx = len(deduped) + ints[c.Value] = newIdx + indexMap[curIdx] = newIdx + deduped = append(deduped, c) + } + case *String: + if newIdx, ok := strings[c.Value]; ok { + indexMap[curIdx] = newIdx + } else { + newIdx = len(deduped) + strings[c.Value] = newIdx + indexMap[curIdx] = newIdx + deduped = append(deduped, c) + } + case *Float: + if newIdx, ok := floats[c.Value]; ok { + indexMap[curIdx] = newIdx + } else { + newIdx = len(deduped) + floats[c.Value] = newIdx + indexMap[curIdx] = newIdx + deduped = append(deduped, c) + } + case *Char: + if newIdx, ok := chars[c.Value]; ok { + indexMap[curIdx] = newIdx + } else { + newIdx = len(deduped) + chars[c.Value] = newIdx + indexMap[curIdx] = newIdx + deduped = append(deduped, c) + } + default: + panic(fmt.Errorf("unsupported top-level constant type: %s", + c.TypeName())) + } + } + + // replace with de-duplicated constants + b.Constants = deduped + + // update CONST instructions with new indexes + // main function + updateConstIndexes(b.MainFunction.Instructions, indexMap) + // other compiled functions in constants + for _, c := range b.Constants { + switch c := c.(type) { + case *CompiledFunction: + updateConstIndexes(c.Instructions, indexMap) + } + } +} + +func fixDecodedObject( + o Object, + modules *ModuleMap, +) (Object, error) { + switch o := o.(type) { + case *Bool: + if o.IsFalsy() { + return FalseValue, nil + } + return TrueValue, nil + case *Undefined: + return UndefinedValue, nil + case *Array: + for i, v := range o.Value { + fv, err := fixDecodedObject(v, modules) + if err != nil { + return nil, err + } + o.Value[i] = fv + } + case *ImmutableArray: + for i, v := range o.Value { + fv, err := fixDecodedObject(v, modules) + if err != nil { + return nil, err + } + o.Value[i] = fv + } + case *Map: + for k, v := range o.Value { + fv, err := fixDecodedObject(v, modules) + if err != nil { + return nil, err + } + o.Value[k] = fv + } + case *ImmutableMap: + modName := inferModuleName(o) + if mod := modules.GetBuiltinModule(modName); mod != nil { + return mod.AsImmutableMap(modName), nil + } + + for k, v := range o.Value { + // encoding of user function not supported + if _, isUserFunction := v.(*UserFunction); isUserFunction { + return nil, fmt.Errorf("user function not decodable") + } + + fv, err := fixDecodedObject(v, modules) + if err != nil { + return nil, err + } + o.Value[k] = fv + } + } + return o, nil +} + +func updateConstIndexes(insts []byte, indexMap map[int]int) { + i := 0 + for i < len(insts) { + op := insts[i] + numOperands := internal.OpcodeOperands[op] + _, read := internal.ReadOperands(numOperands, insts[i+1:]) + + switch op { + case internal.OpConstant: + curIdx := int(insts[i+2]) | int(insts[i+1])<<8 + newIdx, ok := indexMap[curIdx] + if !ok { + panic(fmt.Errorf("constant index not found: %d", curIdx)) + } + copy(insts[i:], internal.MakeInstruction(op, newIdx)) + case internal.OpClosure: + curIdx := int(insts[i+2]) | int(insts[i+1])<<8 + numFree := int(insts[i+3]) + newIdx, ok := indexMap[curIdx] + if !ok { + panic(fmt.Errorf("constant index not found: %d", curIdx)) + } + copy(insts[i:], internal.MakeInstruction(op, newIdx, numFree)) + } + + i += 1 + read + } +} + +func inferModuleName(mod *ImmutableMap) string { + if modName, ok := mod.Value["__module_name__"].(*String); ok { + return modName.Value + } + return "" +} + +func init() { + gob.Register(&internal.SourceFileSet{}) + gob.Register(&internal.SourceFile{}) + gob.Register(&Array{}) + gob.Register(&Bool{}) + gob.Register(&Bytes{}) + gob.Register(&Char{}) + gob.Register(&CompiledFunction{}) + gob.Register(&Error{}) + gob.Register(&Float{}) + gob.Register(&ImmutableArray{}) + gob.Register(&ImmutableMap{}) + gob.Register(&Int{}) + gob.Register(&Map{}) + gob.Register(&String{}) + gob.Register(&Time{}) + gob.Register(&Undefined{}) + gob.Register(&UserFunction{}) +} diff --git a/bytecode_test.go b/bytecode_test.go new file mode 100644 index 0000000..5a221ee --- /dev/null +++ b/bytecode_test.go @@ -0,0 +1,298 @@ +package tengo_test + +import ( + "bytes" + "testing" + "time" + + "github.com/d5/tengo" + "github.com/d5/tengo/internal" + "github.com/d5/tengo/internal/require" +) + +type srcfile struct { + name string + size int +} + +func TestBytecode(t *testing.T) { + testBytecodeSerialization(t, bytecode(concatInsts(), objectsArray())) + + testBytecodeSerialization(t, bytecode( + concatInsts(), objectsArray( + &tengo.Char{Value: 'y'}, + &tengo.Float{Value: 93.11}, + compiledFunction(1, 0, + internal.MakeInstruction(internal.OpConstant, 3), + internal.MakeInstruction(internal.OpSetLocal, 0), + internal.MakeInstruction(internal.OpGetGlobal, 0), + internal.MakeInstruction(internal.OpGetFree, 0)), + &tengo.Float{Value: 39.2}, + &tengo.Int{Value: 192}, + &tengo.String{Value: "bar"}))) + + testBytecodeSerialization(t, bytecodeFileSet( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpSetGlobal, 0), + internal.MakeInstruction(internal.OpConstant, 6), + internal.MakeInstruction(internal.OpPop)), + objectsArray( + &tengo.Int{Value: 55}, + &tengo.Int{Value: 66}, + &tengo.Int{Value: 77}, + &tengo.Int{Value: 88}, + &tengo.ImmutableMap{ + Value: map[string]tengo.Object{ + "array": &tengo.ImmutableArray{ + Value: []tengo.Object{ + &tengo.Int{Value: 1}, + &tengo.Int{Value: 2}, + &tengo.Int{Value: 3}, + tengo.TrueValue, + tengo.FalseValue, + tengo.UndefinedValue, + }, + }, + "true": tengo.TrueValue, + "false": tengo.FalseValue, + "bytes": &tengo.Bytes{Value: make([]byte, 16)}, + "char": &tengo.Char{Value: 'Y'}, + "error": &tengo.Error{Value: &tengo.String{ + Value: "some error", + }}, + "float": &tengo.Float{Value: -19.84}, + "immutable_array": &tengo.ImmutableArray{ + Value: []tengo.Object{ + &tengo.Int{Value: 1}, + &tengo.Int{Value: 2}, + &tengo.Int{Value: 3}, + tengo.TrueValue, + tengo.FalseValue, + tengo.UndefinedValue, + }, + }, + "immutable_map": &tengo.ImmutableMap{ + Value: map[string]tengo.Object{ + "a": &tengo.Int{Value: 1}, + "b": &tengo.Int{Value: 2}, + "c": &tengo.Int{Value: 3}, + "d": tengo.TrueValue, + "e": tengo.FalseValue, + "f": tengo.UndefinedValue, + }, + }, + "int": &tengo.Int{Value: 91}, + "map": &tengo.Map{ + Value: map[string]tengo.Object{ + "a": &tengo.Int{Value: 1}, + "b": &tengo.Int{Value: 2}, + "c": &tengo.Int{Value: 3}, + "d": tengo.TrueValue, + "e": tengo.FalseValue, + "f": tengo.UndefinedValue, + }, + }, + "string": &tengo.String{Value: "foo bar"}, + "time": &tengo.Time{Value: time.Now()}, + "undefined": tengo.UndefinedValue, + }, + }, + compiledFunction(1, 0, + internal.MakeInstruction(internal.OpConstant, 3), + internal.MakeInstruction(internal.OpSetLocal, 0), + internal.MakeInstruction(internal.OpGetGlobal, 0), + internal.MakeInstruction(internal.OpGetFree, 0), + internal.MakeInstruction(internal.OpBinaryOp, 11), + internal.MakeInstruction(internal.OpGetFree, 1), + internal.MakeInstruction(internal.OpBinaryOp, 11), + internal.MakeInstruction(internal.OpGetLocal, 0), + internal.MakeInstruction(internal.OpBinaryOp, 11), + internal.MakeInstruction(internal.OpReturn, 1)), + compiledFunction(1, 0, + internal.MakeInstruction(internal.OpConstant, 2), + internal.MakeInstruction(internal.OpSetLocal, 0), + internal.MakeInstruction(internal.OpGetFree, 0), + internal.MakeInstruction(internal.OpGetLocal, 0), + internal.MakeInstruction(internal.OpClosure, 4, 2), + internal.MakeInstruction(internal.OpReturn, 1)), + compiledFunction(1, 0, + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpSetLocal, 0), + internal.MakeInstruction(internal.OpGetLocal, 0), + internal.MakeInstruction(internal.OpClosure, 5, 1), + internal.MakeInstruction(internal.OpReturn, 1))), + fileSet(srcfile{name: "file1", size: 100}, + srcfile{name: "file2", size: 200}))) +} + +func TestBytecode_RemoveDuplicates(t *testing.T) { + testBytecodeRemoveDuplicates(t, + bytecode( + concatInsts(), objectsArray( + &tengo.Char{Value: 'y'}, + &tengo.Float{Value: 93.11}, + compiledFunction(1, 0, + internal.MakeInstruction(internal.OpConstant, 3), + internal.MakeInstruction(internal.OpSetLocal, 0), + internal.MakeInstruction(internal.OpGetGlobal, 0), + internal.MakeInstruction(internal.OpGetFree, 0)), + &tengo.Float{Value: 39.2}, + &tengo.Int{Value: 192}, + &tengo.String{Value: "bar"})), + bytecode( + concatInsts(), objectsArray( + &tengo.Char{Value: 'y'}, + &tengo.Float{Value: 93.11}, + compiledFunction(1, 0, + internal.MakeInstruction(internal.OpConstant, 3), + internal.MakeInstruction(internal.OpSetLocal, 0), + internal.MakeInstruction(internal.OpGetGlobal, 0), + internal.MakeInstruction(internal.OpGetFree, 0)), + &tengo.Float{Value: 39.2}, + &tengo.Int{Value: 192}, + &tengo.String{Value: "bar"}))) + + testBytecodeRemoveDuplicates(t, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpConstant, 2), + internal.MakeInstruction(internal.OpConstant, 3), + internal.MakeInstruction(internal.OpConstant, 4), + internal.MakeInstruction(internal.OpConstant, 5), + internal.MakeInstruction(internal.OpConstant, 6), + internal.MakeInstruction(internal.OpConstant, 7), + internal.MakeInstruction(internal.OpConstant, 8), + internal.MakeInstruction(internal.OpClosure, 4, 1)), + objectsArray( + &tengo.Int{Value: 1}, + &tengo.Float{Value: 2.0}, + &tengo.Char{Value: '3'}, + &tengo.String{Value: "four"}, + compiledFunction(1, 0, + internal.MakeInstruction(internal.OpConstant, 3), + internal.MakeInstruction(internal.OpConstant, 7), + internal.MakeInstruction(internal.OpSetLocal, 0), + internal.MakeInstruction(internal.OpGetGlobal, 0), + internal.MakeInstruction(internal.OpGetFree, 0)), + &tengo.Int{Value: 1}, + &tengo.Float{Value: 2.0}, + &tengo.Char{Value: '3'}, + &tengo.String{Value: "four"})), + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpConstant, 2), + internal.MakeInstruction(internal.OpConstant, 3), + internal.MakeInstruction(internal.OpConstant, 4), + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpConstant, 2), + internal.MakeInstruction(internal.OpConstant, 3), + internal.MakeInstruction(internal.OpClosure, 4, 1)), + objectsArray( + &tengo.Int{Value: 1}, + &tengo.Float{Value: 2.0}, + &tengo.Char{Value: '3'}, + &tengo.String{Value: "four"}, + compiledFunction(1, 0, + internal.MakeInstruction(internal.OpConstant, 3), + internal.MakeInstruction(internal.OpConstant, 2), + internal.MakeInstruction(internal.OpSetLocal, 0), + internal.MakeInstruction(internal.OpGetGlobal, 0), + internal.MakeInstruction(internal.OpGetFree, 0))))) + + testBytecodeRemoveDuplicates(t, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpConstant, 2), + internal.MakeInstruction(internal.OpConstant, 3), + internal.MakeInstruction(internal.OpConstant, 4)), + objectsArray( + &tengo.Int{Value: 1}, + &tengo.Int{Value: 2}, + &tengo.Int{Value: 3}, + &tengo.Int{Value: 1}, + &tengo.Int{Value: 3})), + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpConstant, 2), + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpConstant, 2)), + objectsArray( + &tengo.Int{Value: 1}, + &tengo.Int{Value: 2}, + &tengo.Int{Value: 3}))) +} + +func TestBytecode_CountObjects(t *testing.T) { + b := bytecode( + concatInsts(), + objectsArray( + &tengo.Int{Value: 55}, + &tengo.Int{Value: 66}, + &tengo.Int{Value: 77}, + &tengo.Int{Value: 88}, + compiledFunction(1, 0, + internal.MakeInstruction(internal.OpConstant, 3), + internal.MakeInstruction(internal.OpReturn, 1)), + compiledFunction(1, 0, + internal.MakeInstruction(internal.OpConstant, 2), + internal.MakeInstruction(internal.OpReturn, 1)), + compiledFunction(1, 0, + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpReturn, 1)))) + require.Equal(t, 7, b.CountObjects()) +} + +func fileSet(files ...srcfile) *internal.SourceFileSet { + fileSet := internal.NewFileSet() + for _, f := range files { + fileSet.AddFile(f.name, -1, f.size) + } + return fileSet +} + +func bytecodeFileSet( + instructions []byte, + constants []tengo.Object, + fileSet *internal.SourceFileSet, +) *tengo.Bytecode { + return &tengo.Bytecode{ + FileSet: fileSet, + MainFunction: &tengo.CompiledFunction{Instructions: instructions}, + Constants: constants, + } +} + +func testBytecodeRemoveDuplicates( + t *testing.T, + input, expected *tengo.Bytecode, +) { + input.RemoveDuplicates() + + require.Equal(t, expected.FileSet, input.FileSet) + require.Equal(t, expected.MainFunction, input.MainFunction) + require.Equal(t, expected.Constants, input.Constants) +} + +func testBytecodeSerialization(t *testing.T, b *tengo.Bytecode) { + var buf bytes.Buffer + err := b.Encode(&buf) + require.NoError(t, err) + + r := &tengo.Bytecode{} + err = r.Decode(bytes.NewReader(buf.Bytes()), nil) + require.NoError(t, err) + + require.Equal(t, b.FileSet, r.FileSet) + require.Equal(t, b.MainFunction, r.MainFunction) + require.Equal(t, b.Constants, r.Constants) +} diff --git a/cli/cli.go b/cli/cli.go deleted file mode 100644 index 858982f..0000000 --- a/cli/cli.go +++ /dev/null @@ -1,329 +0,0 @@ -package cli - -import ( - "bufio" - "bytes" - "fmt" - "io" - "io/ioutil" - "os" - "path/filepath" - "strings" - - "github.com/d5/tengo/compiler" - "github.com/d5/tengo/compiler/ast" - "github.com/d5/tengo/compiler/parser" - "github.com/d5/tengo/compiler/source" - "github.com/d5/tengo/objects" - "github.com/d5/tengo/runtime" -) - -const ( - sourceFileExt = ".tengo" - replPrompt = ">> " -) - -// Options represent CLI options -type Options struct { - // Compile output file - CompileOutput string - - // Show help flag - ShowHelp bool - - // Show version flag - ShowVersion bool - - // Input file - InputFile string - - // Version - Version string - - // Import modules - Modules *objects.ModuleMap -} - -// Run CLI -func Run(options *Options) { - if options.ShowHelp { - doHelp() - os.Exit(2) - } else if options.ShowVersion { - fmt.Println(options.Version) - return - } - - if options.InputFile == "" { - // REPL - RunREPL(options.Modules, os.Stdin, os.Stdout) - return - } - - inputData, err := ioutil.ReadFile(options.InputFile) - if err != nil { - _, _ = fmt.Fprintf(os.Stderr, "Error reading input file: %s", err.Error()) - os.Exit(1) - } - - if options.CompileOutput != "" { - if err := CompileOnly(options.Modules, inputData, options.InputFile, options.CompileOutput); err != nil { - _, _ = fmt.Fprintln(os.Stderr, err.Error()) - os.Exit(1) - } - } else if filepath.Ext(options.InputFile) == sourceFileExt { - if err := CompileAndRun(options.Modules, inputData, options.InputFile); err != nil { - _, _ = fmt.Fprintln(os.Stderr, err.Error()) - os.Exit(1) - } - } else { - if err := RunCompiled(options.Modules, inputData); err != nil { - _, _ = fmt.Fprintln(os.Stderr, err.Error()) - os.Exit(1) - } - } -} - -func doHelp() { - fmt.Println("Usage:") - fmt.Println() - fmt.Println(" tengo [flags] {input-file}") - fmt.Println() - fmt.Println("Flags:") - fmt.Println() - fmt.Println(" -o compile output file") - fmt.Println(" -version show version") - fmt.Println() - fmt.Println("Examples:") - fmt.Println() - fmt.Println(" tengo") - fmt.Println() - fmt.Println(" Start Tengo REPL") - fmt.Println() - fmt.Println(" tengo myapp.tengo") - fmt.Println() - fmt.Println(" Compile and run source file (myapp.tengo)") - fmt.Println(" Source file must have .tengo extension") - fmt.Println() - fmt.Println(" tengo -o myapp myapp.tengo") - fmt.Println() - fmt.Println(" Compile source file (myapp.tengo) into bytecode file (myapp)") - fmt.Println() - fmt.Println(" tengo myapp") - fmt.Println() - fmt.Println(" Run bytecode file (myapp)") - fmt.Println() - fmt.Println() -} - -// CompileOnly compiles the source code and writes the compiled binary into outputFile. -func CompileOnly(modules *objects.ModuleMap, data []byte, inputFile, outputFile string) (err error) { - bytecode, err := compileSrc(modules, data, filepath.Base(inputFile)) - if err != nil { - return - } - - if outputFile == "" { - outputFile = basename(inputFile) + ".out" - } - - out, err := os.OpenFile(outputFile, os.O_CREATE|os.O_WRONLY, os.ModePerm) - if err != nil { - return - } - defer func() { - if err != nil { - _ = out.Close() - } else { - err = out.Close() - } - }() - - err = bytecode.Encode(out) - if err != nil { - return - } - - fmt.Println(outputFile) - - return -} - -// CompileAndRun compiles the source code and executes it. -func CompileAndRun(modules *objects.ModuleMap, data []byte, inputFile string) (err error) { - bytecode, err := compileSrc(modules, data, filepath.Base(inputFile)) - if err != nil { - return - } - - machine := runtime.NewVM(bytecode, nil, -1) - - err = machine.Run() - if err != nil { - return - } - - return -} - -// RunCompiled reads the compiled binary from file and executes it. -func RunCompiled(modules *objects.ModuleMap, data []byte) (err error) { - bytecode := &compiler.Bytecode{} - err = bytecode.Decode(bytes.NewReader(data), modules) - if err != nil { - return - } - - machine := runtime.NewVM(bytecode, nil, -1) - - err = machine.Run() - if err != nil { - return - } - - return -} - -// RunREPL starts REPL. -func RunREPL(modules *objects.ModuleMap, in io.Reader, out io.Writer) { - stdin := bufio.NewScanner(in) - - fileSet := source.NewFileSet() - globals := make([]objects.Object, runtime.GlobalsSize) - - symbolTable := compiler.NewSymbolTable() - for idx, fn := range objects.Builtins { - symbolTable.DefineBuiltin(idx, fn.Name) - } - - // embed println function - symbol := symbolTable.Define("__repl_println__") - globals[symbol.Index] = &objects.UserFunction{ - Name: "println", - Value: func(args ...objects.Object) (ret objects.Object, err error) { - var printArgs []interface{} - for _, arg := range args { - if _, isUndefined := arg.(*objects.Undefined); isUndefined { - printArgs = append(printArgs, "") - } else { - s, _ := objects.ToString(arg) - printArgs = append(printArgs, s) - } - } - - printArgs = append(printArgs, "\n") - _, _ = fmt.Print(printArgs...) - - return - }, - } - - var constants []objects.Object - - for { - _, _ = fmt.Fprint(out, replPrompt) - - scanned := stdin.Scan() - if !scanned { - return - } - - line := stdin.Text() - - srcFile := fileSet.AddFile("repl", -1, len(line)) - p := parser.NewParser(srcFile, []byte(line), nil) - file, err := p.ParseFile() - if err != nil { - _, _ = fmt.Fprintln(out, err.Error()) - continue - } - - file = addPrints(file) - - c := compiler.NewCompiler(srcFile, symbolTable, constants, modules, nil) - if err := c.Compile(file); err != nil { - _, _ = fmt.Fprintln(out, err.Error()) - continue - } - - bytecode := c.Bytecode() - - machine := runtime.NewVM(bytecode, globals, -1) - if err := machine.Run(); err != nil { - _, _ = fmt.Fprintln(out, err.Error()) - continue - } - - constants = bytecode.Constants - } -} - -func compileSrc(modules *objects.ModuleMap, src []byte, filename string) (*compiler.Bytecode, error) { - fileSet := source.NewFileSet() - srcFile := fileSet.AddFile(filename, -1, len(src)) - - p := parser.NewParser(srcFile, src, nil) - file, err := p.ParseFile() - if err != nil { - return nil, err - } - - c := compiler.NewCompiler(srcFile, nil, nil, modules, nil) - c.EnableFileImport(true) - - if err := c.Compile(file); err != nil { - return nil, err - } - - bytecode := c.Bytecode() - bytecode.RemoveDuplicates() - - return bytecode, nil -} - -func addPrints(file *ast.File) *ast.File { - var stmts []ast.Stmt - - for _, s := range file.Stmts { - switch s := s.(type) { - case *ast.ExprStmt: - stmts = append(stmts, &ast.ExprStmt{ - Expr: &ast.CallExpr{ - Func: &ast.Ident{Name: "__repl_println__"}, - Args: []ast.Expr{s.Expr}, - }, - }) - - case *ast.AssignStmt: - stmts = append(stmts, s) - - stmts = append(stmts, &ast.ExprStmt{ - Expr: &ast.CallExpr{ - Func: &ast.Ident{ - Name: "__repl_println__", - }, - Args: s.LHS, - }, - }) - - default: - stmts = append(stmts, s) - } - } - - return &ast.File{ - InputFile: file.InputFile, - Stmts: stmts, - } -} - -func basename(s string) string { - s = filepath.Base(s) - - n := strings.LastIndexByte(s, '.') - if n > 0 { - return s[:n] - } - - return s -} diff --git a/cli/cli_test.go b/cli/cli_test.go deleted file mode 100644 index d6ff590..0000000 --- a/cli/cli_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package cli_test - -import ( - "io/ioutil" - "os" - "path/filepath" - "regexp" - "testing" - - "github.com/d5/tengo/assert" - "github.com/d5/tengo/cli" - "github.com/d5/tengo/stdlib" -) - -func TestCLICompileAndRun(t *testing.T) { - tempDir := filepath.Join(os.TempDir(), "tengo_tests") - _ = os.MkdirAll(tempDir, os.ModePerm) - binFile := filepath.Join(tempDir, "cli_bin") - outFile := filepath.Join(tempDir, "cli_out") - defer func() { - _ = os.RemoveAll(tempDir) - }() - - src := []byte(` -os := import("os") -rand := import("rand") -times := import("times") - -rand.seed(times.time_nanosecond(times.now())) - -rand_num := func() { - return rand.intn(100) -} - -file := os.create("` + outFile + `") -file.write_string("random number is " + rand_num()) -file.close() -`) - - mods := stdlib.GetModuleMap(stdlib.AllModuleNames()...) - - err := cli.CompileOnly(mods, src, "src", binFile) - if !assert.NoError(t, err) { - return - } - - compiledBin, err := ioutil.ReadFile(binFile) - if !assert.NoError(t, err) { - return - } - - err = cli.RunCompiled(mods, compiledBin) - if !assert.NoError(t, err) { - return - } - - read, err := ioutil.ReadFile(outFile) - if !assert.NoError(t, err) { - return - } - ok, err := regexp.Match(`^random number is \d+$`, read) - assert.NoError(t, err) - assert.True(t, ok, string(read)) -} diff --git a/cmd/bench/main.go b/cmd/bench/main.go index 4c247bb..b36f12e 100644 --- a/cmd/bench/main.go +++ b/cmd/bench/main.go @@ -4,12 +4,8 @@ import ( "fmt" "time" - "github.com/d5/tengo/compiler" - "github.com/d5/tengo/compiler/ast" - "github.com/d5/tengo/compiler/parser" - "github.com/d5/tengo/compiler/source" - "github.com/d5/tengo/objects" - "github.com/d5/tengo/runtime" + "github.com/d5/tengo" + "github.com/d5/tengo/internal" ) func main() { @@ -40,8 +36,9 @@ fib := func(x) { panic(err) } - if nativeResult != int(result.(*objects.Int).Value) { - panic(fmt.Errorf("wrong result: %d != %d", nativeResult, int(result.(*objects.Int).Value))) + if nativeResult != int(result.(*tengo.Int).Value) { + panic(fmt.Errorf("wrong result: %d != %d", nativeResult, + int(result.(*tengo.Int).Value))) } fmt.Println("-------------------------------------") @@ -76,8 +73,9 @@ fib := func(x, s) { panic(err) } - if nativeResult != int(result.(*objects.Int).Value) { - panic(fmt.Errorf("wrong result: %d != %d", nativeResult, int(result.(*objects.Int).Value))) + if nativeResult != int(result.(*tengo.Int).Value) { + panic(fmt.Errorf("wrong result: %d != %d", nativeResult, + int(result.(*tengo.Int).Value))) } fmt.Println("-------------------------------------") @@ -112,8 +110,9 @@ fib := func(x, a, b) { panic(err) } - if nativeResult != int(result.(*objects.Int).Value) { - panic(fmt.Errorf("wrong result: %d != %d", nativeResult, int(result.(*objects.Int).Value))) + if nativeResult != int(result.(*tengo.Int).Value) { + panic(fmt.Errorf("wrong result: %d != %d", nativeResult, + int(result.(*tengo.Int).Value))) } fmt.Println("-------------------------------------") @@ -155,14 +154,22 @@ func fibTC2(n, a, b int) int { } } -func runBench(input []byte) (parseTime time.Duration, compileTime time.Duration, runTime time.Duration, result objects.Object, err error) { - var astFile *ast.File +func runBench( + input []byte, +) ( + parseTime time.Duration, + compileTime time.Duration, + runTime time.Duration, + result tengo.Object, + err error, +) { + var astFile *internal.File parseTime, astFile, err = parse(input) if err != nil { return } - var bytecode *compiler.Bytecode + var bytecode *tengo.Bytecode compileTime, bytecode, err = compileFile(astFile) if err != nil { return @@ -173,13 +180,13 @@ func runBench(input []byte) (parseTime time.Duration, compileTime time.Duration, return } -func parse(input []byte) (time.Duration, *ast.File, error) { - fileSet := source.NewFileSet() +func parse(input []byte) (time.Duration, *internal.File, error) { + fileSet := internal.NewFileSet() inputFile := fileSet.AddFile("bench", -1, len(input)) start := time.Now() - p := parser.NewParser(inputFile, input, nil) + p := internal.NewParser(inputFile, input, nil) file, err := p.ParseFile() if err != nil { return time.Since(start), nil, err @@ -188,13 +195,13 @@ func parse(input []byte) (time.Duration, *ast.File, error) { return time.Since(start), file, nil } -func compileFile(file *ast.File) (time.Duration, *compiler.Bytecode, error) { - symTable := compiler.NewSymbolTable() +func compileFile(file *internal.File) (time.Duration, *tengo.Bytecode, error) { + symTable := internal.NewSymbolTable() symTable.Define("out") start := time.Now() - c := compiler.NewCompiler(file.InputFile, symTable, nil, nil, nil) + c := tengo.NewCompiler(file.InputFile, symTable, nil, nil, nil) if err := c.Compile(file); err != nil { return time.Since(start), nil, err } @@ -205,12 +212,14 @@ func compileFile(file *ast.File) (time.Duration, *compiler.Bytecode, error) { return time.Since(start), bytecode, nil } -func runVM(bytecode *compiler.Bytecode) (time.Duration, objects.Object, error) { - globals := make([]objects.Object, runtime.GlobalsSize) +func runVM( + bytecode *tengo.Bytecode, +) (time.Duration, tengo.Object, error) { + globals := make([]tengo.Object, tengo.GlobalsSize) start := time.Now() - v := runtime.NewVM(bytecode, globals, -1) + v := tengo.NewVM(bytecode, globals, -1) if err := v.Run(); err != nil { return time.Since(start), nil, err } diff --git a/cmd/tengo/main.go b/cmd/tengo/main.go index 65cab66..e6e514d 100644 --- a/cmd/tengo/main.go +++ b/cmd/tengo/main.go @@ -1,12 +1,26 @@ package main import ( + "bufio" + "bytes" "flag" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" - "github.com/d5/tengo/cli" + "github.com/d5/tengo" + "github.com/d5/tengo/internal" "github.com/d5/tengo/stdlib" ) +const ( + sourceFileExt = ".tengo" + replPrompt = ">> " +) + var ( compileOutput string showHelp bool @@ -22,12 +36,274 @@ func init() { } func main() { - cli.Run(&cli.Options{ - ShowHelp: showHelp, - ShowVersion: showVersion, - Version: version, - CompileOutput: compileOutput, - Modules: stdlib.GetModuleMap(stdlib.AllModuleNames()...), - InputFile: flag.Arg(0), - }) + if showHelp { + doHelp() + os.Exit(2) + } else if showVersion { + fmt.Println(version) + return + } + + modules := stdlib.GetModuleMap(stdlib.AllModuleNames()...) + inputFile := flag.Arg(0) + if inputFile == "" { + // REPL + RunREPL(modules, os.Stdin, os.Stdout) + return + } + + inputData, err := ioutil.ReadFile(inputFile) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, + "Error reading input file: %s", err.Error()) + os.Exit(1) + } + + if compileOutput != "" { + err := CompileOnly(modules, inputData, inputFile, + compileOutput) + if err != nil { + _, _ = fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } + } else if filepath.Ext(inputFile) == sourceFileExt { + err := CompileAndRun(modules, inputData, inputFile) + if err != nil { + _, _ = fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } + } else { + if err := RunCompiled(modules, inputData); err != nil { + _, _ = fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } + } +} + +// CompileOnly compiles the source code and writes the compiled binary into +// outputFile. +func CompileOnly( + modules *tengo.ModuleMap, + data []byte, + inputFile, outputFile string, +) (err error) { + bytecode, err := compileSrc(modules, data, filepath.Base(inputFile)) + if err != nil { + return + } + + if outputFile == "" { + outputFile = basename(inputFile) + ".out" + } + + out, err := os.OpenFile(outputFile, os.O_CREATE|os.O_WRONLY, os.ModePerm) + if err != nil { + return + } + defer func() { + if err != nil { + _ = out.Close() + } else { + err = out.Close() + } + }() + + err = bytecode.Encode(out) + if err != nil { + return + } + fmt.Println(outputFile) + return +} + +// CompileAndRun compiles the source code and executes it. +func CompileAndRun( + modules *tengo.ModuleMap, + data []byte, + inputFile string, +) (err error) { + bytecode, err := compileSrc(modules, data, filepath.Base(inputFile)) + if err != nil { + return + } + + machine := tengo.NewVM(bytecode, nil, -1) + err = machine.Run() + return +} + +// RunCompiled reads the compiled binary from file and executes it. +func RunCompiled(modules *tengo.ModuleMap, data []byte) (err error) { + bytecode := &tengo.Bytecode{} + err = bytecode.Decode(bytes.NewReader(data), modules) + if err != nil { + return + } + + machine := tengo.NewVM(bytecode, nil, -1) + err = machine.Run() + return +} + +// RunREPL starts REPL. +func RunREPL(modules *tengo.ModuleMap, in io.Reader, out io.Writer) { + stdin := bufio.NewScanner(in) + fileSet := internal.NewFileSet() + globals := make([]tengo.Object, tengo.GlobalsSize) + symbolTable := internal.NewSymbolTable() + for idx, fn := range tengo.GetAllBuiltinFunctions() { + symbolTable.DefineBuiltin(idx, fn.Name) + } + + // embed println function + symbol := symbolTable.Define("__repl_println__") + globals[symbol.Index] = &tengo.UserFunction{ + Name: "println", + Value: func(args ...tengo.Object) (ret tengo.Object, err error) { + var printArgs []interface{} + for _, arg := range args { + if _, isUndefined := arg.(*tengo.Undefined); isUndefined { + printArgs = append(printArgs, "") + } else { + s, _ := tengo.ToString(arg) + printArgs = append(printArgs, s) + } + } + printArgs = append(printArgs, "\n") + _, _ = fmt.Print(printArgs...) + return + }, + } + + var constants []tengo.Object + for { + _, _ = fmt.Fprint(out, replPrompt) + scanned := stdin.Scan() + if !scanned { + return + } + + line := stdin.Text() + srcFile := fileSet.AddFile("repl", -1, len(line)) + p := internal.NewParser(srcFile, []byte(line), nil) + file, err := p.ParseFile() + if err != nil { + _, _ = fmt.Fprintln(out, err.Error()) + continue + } + + file = addPrints(file) + c := tengo.NewCompiler(srcFile, symbolTable, constants, modules, nil) + if err := c.Compile(file); err != nil { + _, _ = fmt.Fprintln(out, err.Error()) + continue + } + + bytecode := c.Bytecode() + machine := tengo.NewVM(bytecode, globals, -1) + if err := machine.Run(); err != nil { + _, _ = fmt.Fprintln(out, err.Error()) + continue + } + constants = bytecode.Constants + } +} + +func compileSrc( + modules *tengo.ModuleMap, + src []byte, + filename string, +) (*tengo.Bytecode, error) { + fileSet := internal.NewFileSet() + srcFile := fileSet.AddFile(filename, -1, len(src)) + + p := internal.NewParser(srcFile, src, nil) + file, err := p.ParseFile() + if err != nil { + return nil, err + } + + c := tengo.NewCompiler(srcFile, nil, nil, modules, nil) + c.EnableFileImport(true) + + if err := c.Compile(file); err != nil { + return nil, err + } + + bytecode := c.Bytecode() + bytecode.RemoveDuplicates() + return bytecode, nil +} + +func doHelp() { + fmt.Println("Usage:") + fmt.Println() + fmt.Println(" tengo [flags] {input-file}") + fmt.Println() + fmt.Println("Flags:") + fmt.Println() + fmt.Println(" -o compile output file") + fmt.Println(" -version show version") + fmt.Println() + fmt.Println("Examples:") + fmt.Println() + fmt.Println(" tengo") + fmt.Println() + fmt.Println(" Start Tengo REPL") + fmt.Println() + fmt.Println(" tengo myapp.tengo") + fmt.Println() + fmt.Println(" Compile and run source file (myapp.tengo)") + fmt.Println(" Source file must have .tengo extension") + fmt.Println() + fmt.Println(" tengo -o myapp myapp.tengo") + fmt.Println() + fmt.Println(" Compile source file (myapp.tengo) into bytecode file (myapp)") + fmt.Println() + fmt.Println(" tengo myapp") + fmt.Println() + fmt.Println(" Run bytecode file (myapp)") + fmt.Println() + fmt.Println() +} + +func addPrints(file *internal.File) *internal.File { + var stmts []internal.Stmt + for _, s := range file.Stmts { + switch s := s.(type) { + case *internal.ExprStmt: + stmts = append(stmts, &internal.ExprStmt{ + Expr: &internal.CallExpr{ + Func: &internal.Ident{Name: "__repl_println__"}, + Args: []internal.Expr{s.Expr}, + }, + }) + case *internal.AssignStmt: + stmts = append(stmts, s) + + stmts = append(stmts, &internal.ExprStmt{ + Expr: &internal.CallExpr{ + Func: &internal.Ident{ + Name: "__repl_println__", + }, + Args: s.LHS, + }, + }) + default: + stmts = append(stmts, s) + } + } + return &internal.File{ + InputFile: file.InputFile, + Stmts: stmts, + } +} + +func basename(s string) string { + s = filepath.Base(s) + n := strings.LastIndexByte(s, '.') + if n > 0 { + return s[:n] + } + return s } diff --git a/cmd/tengomin/main.go b/cmd/tengomin/main.go deleted file mode 100644 index ae9a3ac..0000000 --- a/cmd/tengomin/main.go +++ /dev/null @@ -1,31 +0,0 @@ -package main - -import ( - "flag" - - "github.com/d5/tengo/cli" -) - -var ( - compileOutput string - showHelp bool - showVersion bool - version = "dev" -) - -func init() { - flag.BoolVar(&showHelp, "help", false, "Show help") - flag.StringVar(&compileOutput, "o", "", "Compile output file") - flag.BoolVar(&showVersion, "version", false, "Show version") - flag.Parse() -} - -func main() { - cli.Run(&cli.Options{ - ShowHelp: showHelp, - ShowVersion: showVersion, - Version: version, - CompileOutput: compileOutput, - InputFile: flag.Arg(0), - }) -} diff --git a/compiler.go b/compiler.go new file mode 100644 index 0000000..158d5dc --- /dev/null +++ b/compiler.go @@ -0,0 +1,1285 @@ +package tengo + +import ( + "fmt" + "io" + "io/ioutil" + "path/filepath" + "reflect" + "strings" + + "github.com/d5/tengo/internal" + "github.com/d5/tengo/internal/token" +) + +// Compiler compiles the AST into a bytecode. +type Compiler struct { + file *internal.SourceFile + parent *Compiler + modulePath string + constants []Object + symbolTable *internal.SymbolTable + scopes []internal.CompilationScope + scopeIndex int + modules *ModuleMap + compiledModules map[string]*CompiledFunction + allowFileImport bool + loops []*internal.Loop + loopIndex int + trace io.Writer + indent int +} + +// NewCompiler creates a Compiler. +func NewCompiler( + file *internal.SourceFile, + symbolTable *internal.SymbolTable, + constants []Object, + modules *ModuleMap, + trace io.Writer, +) *Compiler { + mainScope := internal.CompilationScope{ + SymbolInit: make(map[string]bool), + SourceMap: make(map[int]internal.Pos), + } + + // symbol table + if symbolTable == nil { + symbolTable = internal.NewSymbolTable() + } + + // add builtin functions to the symbol table + for idx, fn := range builtinFuncs { + symbolTable.DefineBuiltin(idx, fn.Name) + } + + // builtin modules + if modules == nil { + modules = NewModuleMap() + } + + return &Compiler{ + file: file, + symbolTable: symbolTable, + constants: constants, + scopes: []internal.CompilationScope{mainScope}, + scopeIndex: 0, + loopIndex: -1, + trace: trace, + modules: modules, + compiledModules: make(map[string]*CompiledFunction), + } +} + +// Compile compiles the AST node. +func (c *Compiler) Compile(node internal.Node) error { + if c.trace != nil { + if node != nil { + defer untracec(tracec(c, fmt.Sprintf("%s (%s)", + node.String(), reflect.TypeOf(node).Elem().Name()))) + } else { + defer untracec(tracec(c, "")) + } + } + + switch node := node.(type) { + case *internal.File: + for _, stmt := range node.Stmts { + if err := c.Compile(stmt); err != nil { + return err + } + } + case *internal.ExprStmt: + if err := c.Compile(node.Expr); err != nil { + return err + } + c.emit(node, internal.OpPop) + case *internal.IncDecStmt: + op := token.AddAssign + if node.Token == token.Dec { + op = token.SubAssign + } + return c.compileAssign(node, []internal.Expr{node.Expr}, + []internal.Expr{&internal.IntLit{Value: 1}}, op) + case *internal.ParenExpr: + if err := c.Compile(node.Expr); err != nil { + return err + } + case *internal.BinaryExpr: + if node.Token == token.LAnd || node.Token == token.LOr { + return c.compileLogical(node) + } + if node.Token == token.Less { + if err := c.Compile(node.RHS); err != nil { + return err + } + if err := c.Compile(node.LHS); err != nil { + return err + } + c.emit(node, internal.OpBinaryOp, int(token.Greater)) + return nil + } else if node.Token == token.LessEq { + if err := c.Compile(node.RHS); err != nil { + return err + } + if err := c.Compile(node.LHS); err != nil { + return err + } + c.emit(node, internal.OpBinaryOp, int(token.GreaterEq)) + return nil + } + if err := c.Compile(node.LHS); err != nil { + return err + } + if err := c.Compile(node.RHS); err != nil { + return err + } + + switch node.Token { + case token.Add: + c.emit(node, internal.OpBinaryOp, int(token.Add)) + case token.Sub: + c.emit(node, internal.OpBinaryOp, int(token.Sub)) + case token.Mul: + c.emit(node, internal.OpBinaryOp, int(token.Mul)) + case token.Quo: + c.emit(node, internal.OpBinaryOp, int(token.Quo)) + case token.Rem: + c.emit(node, internal.OpBinaryOp, int(token.Rem)) + case token.Greater: + c.emit(node, internal.OpBinaryOp, int(token.Greater)) + case token.GreaterEq: + c.emit(node, internal.OpBinaryOp, int(token.GreaterEq)) + case token.Equal: + c.emit(node, internal.OpEqual) + case token.NotEqual: + c.emit(node, internal.OpNotEqual) + case token.And: + c.emit(node, internal.OpBinaryOp, int(token.And)) + case token.Or: + c.emit(node, internal.OpBinaryOp, int(token.Or)) + case token.Xor: + c.emit(node, internal.OpBinaryOp, int(token.Xor)) + case token.AndNot: + c.emit(node, internal.OpBinaryOp, int(token.AndNot)) + case token.Shl: + c.emit(node, internal.OpBinaryOp, int(token.Shl)) + case token.Shr: + c.emit(node, internal.OpBinaryOp, int(token.Shr)) + default: + return c.errorf(node, "invalid binary operator: %s", + node.Token.String()) + } + case *internal.IntLit: + c.emit(node, internal.OpConstant, + c.addConstant(&Int{Value: node.Value})) + case *internal.FloatLit: + c.emit(node, internal.OpConstant, + c.addConstant(&Float{Value: node.Value})) + case *internal.BoolLit: + if node.Value { + c.emit(node, internal.OpTrue) + } else { + c.emit(node, internal.OpFalse) + } + case *internal.StringLit: + if len(node.Value) > MaxStringLen { + return c.error(node, ErrStringLimit) + } + c.emit(node, internal.OpConstant, + c.addConstant(&String{Value: node.Value})) + case *internal.CharLit: + c.emit(node, internal.OpConstant, + c.addConstant(&Char{Value: node.Value})) + case *internal.UndefinedLit: + c.emit(node, internal.OpNull) + case *internal.UnaryExpr: + if err := c.Compile(node.Expr); err != nil { + return err + } + + switch node.Token { + case token.Not: + c.emit(node, internal.OpLNot) + case token.Sub: + c.emit(node, internal.OpMinus) + case token.Xor: + c.emit(node, internal.OpBComplement) + case token.Add: + // do nothing? + default: + return c.errorf(node, + "invalid unary operator: %s", node.Token.String()) + } + case *internal.IfStmt: + // open new symbol table for the statement + c.symbolTable = c.symbolTable.Fork(true) + defer func() { + c.symbolTable = c.symbolTable.Parent(false) + }() + + if node.Init != nil { + if err := c.Compile(node.Init); err != nil { + return err + } + } + if err := c.Compile(node.Cond); err != nil { + return err + } + + // first jump placeholder + jumpPos1 := c.emit(node, internal.OpJumpFalsy, 0) + if err := c.Compile(node.Body); err != nil { + return err + } + if node.Else != nil { + // second jump placeholder + jumpPos2 := c.emit(node, internal.OpJump, 0) + + // update first jump offset + curPos := len(c.currentInstructions()) + c.changeOperand(jumpPos1, curPos) + if err := c.Compile(node.Else); err != nil { + return err + } + + // update second jump offset + curPos = len(c.currentInstructions()) + c.changeOperand(jumpPos2, curPos) + } else { + // update first jump offset + curPos := len(c.currentInstructions()) + c.changeOperand(jumpPos1, curPos) + } + case *internal.ForStmt: + return c.compileForStmt(node) + case *internal.ForInStmt: + return c.compileForInStmt(node) + case *internal.BranchStmt: + if node.Token == token.Break { + curLoop := c.currentLoop() + if curLoop == nil { + return c.errorf(node, "break not allowed outside loop") + } + pos := c.emit(node, internal.OpJump, 0) + curLoop.Breaks = append(curLoop.Breaks, pos) + } else if node.Token == token.Continue { + curLoop := c.currentLoop() + if curLoop == nil { + return c.errorf(node, "continue not allowed outside loop") + } + pos := c.emit(node, internal.OpJump, 0) + curLoop.Continues = append(curLoop.Continues, pos) + } else { + panic(fmt.Errorf("invalid branch statement: %s", + node.Token.String())) + } + case *internal.BlockStmt: + if len(node.Stmts) == 0 { + return nil + } + + c.symbolTable = c.symbolTable.Fork(true) + defer func() { + c.symbolTable = c.symbolTable.Parent(false) + }() + + for _, stmt := range node.Stmts { + if err := c.Compile(stmt); err != nil { + return err + } + } + case *internal.AssignStmt: + err := c.compileAssign(node, node.LHS, node.RHS, node.Token) + if err != nil { + return err + } + case *internal.Ident: + symbol, _, ok := c.symbolTable.Resolve(node.Name) + if !ok { + return c.errorf(node, "unresolved reference '%s'", node.Name) + } + + switch symbol.Scope { + case internal.ScopeGlobal: + c.emit(node, internal.OpGetGlobal, symbol.Index) + case internal.ScopeLocal: + c.emit(node, internal.OpGetLocal, symbol.Index) + case internal.ScopeBuiltin: + c.emit(node, internal.OpGetBuiltin, symbol.Index) + case internal.ScopeFree: + c.emit(node, internal.OpGetFree, symbol.Index) + } + case *internal.ArrayLit: + for _, elem := range node.Elements { + if err := c.Compile(elem); err != nil { + return err + } + } + c.emit(node, internal.OpArray, len(node.Elements)) + case *internal.MapLit: + for _, elt := range node.Elements { + // key + if len(elt.Key) > MaxStringLen { + return c.error(node, ErrStringLimit) + } + c.emit(node, internal.OpConstant, + c.addConstant(&String{Value: elt.Key})) + + // value + if err := c.Compile(elt.Value); err != nil { + return err + } + } + c.emit(node, internal.OpMap, len(node.Elements)*2) + + case *internal.SelectorExpr: // selector on RHS side + if err := c.Compile(node.Expr); err != nil { + return err + } + if err := c.Compile(node.Sel); err != nil { + return err + } + c.emit(node, internal.OpIndex) + case *internal.IndexExpr: + if err := c.Compile(node.Expr); err != nil { + return err + } + if err := c.Compile(node.Index); err != nil { + return err + } + c.emit(node, internal.OpIndex) + case *internal.SliceExpr: + if err := c.Compile(node.Expr); err != nil { + return err + } + if node.Low != nil { + if err := c.Compile(node.Low); err != nil { + return err + } + } else { + c.emit(node, internal.OpNull) + } + if node.High != nil { + if err := c.Compile(node.High); err != nil { + return err + } + } else { + c.emit(node, internal.OpNull) + } + c.emit(node, internal.OpSliceIndex) + case *internal.FuncLit: + c.enterScope() + + for _, p := range node.Type.Params.List { + s := c.symbolTable.Define(p.Name) + + // function arguments is not assigned directly. + s.LocalAssigned = true + } + + if err := c.Compile(node.Body); err != nil { + return err + } + + // code optimization + c.optimizeFunc(node) + + freeSymbols := c.symbolTable.FreeSymbols() + numLocals := c.symbolTable.MaxSymbols() + instructions, sourceMap := c.leaveScope() + + for _, s := range freeSymbols { + switch s.Scope { + case internal.ScopeLocal: + if !s.LocalAssigned { + // Here, the closure is capturing a local variable that's + // not yet assigned its value. One example is a local + // recursive function: + // + // func() { + // foo := func(x) { + // // .. + // return foo(x-1) + // } + // } + // + // which translate into + // + // 0000 GETL 0 + // 0002 CLOSURE ? 1 + // 0006 DEFL 0 + // + // . So the local variable (0) is being captured before + // it's assigned the value. + // + // Solution is to transform the code into something like + // this: + // + // func() { + // foo := undefined + // foo = func(x) { + // // .. + // return foo(x-1) + // } + // } + // + // that is equivalent to + // + // 0000 NULL + // 0001 DEFL 0 + // 0003 GETL 0 + // 0005 CLOSURE ? 1 + // 0009 SETL 0 + // + c.emit(node, internal.OpNull) + c.emit(node, internal.OpDefineLocal, s.Index) + s.LocalAssigned = true + } + c.emit(node, internal.OpGetLocalPtr, s.Index) + case internal.ScopeFree: + c.emit(node, internal.OpGetFreePtr, s.Index) + } + } + + compiledFunction := &CompiledFunction{ + Instructions: instructions, + NumLocals: numLocals, + NumParameters: len(node.Type.Params.List), + VarArgs: node.Type.Params.VarArgs, + SourceMap: sourceMap, + } + if len(freeSymbols) > 0 { + c.emit(node, internal.OpClosure, + c.addConstant(compiledFunction), len(freeSymbols)) + } else { + c.emit(node, internal.OpConstant, c.addConstant(compiledFunction)) + } + case *internal.ReturnStmt: + if c.symbolTable.Parent(true) == nil { + // outside the function + return c.errorf(node, "return not allowed outside function") + } + + if node.Result == nil { + c.emit(node, internal.OpReturn, 0) + } else { + if err := c.Compile(node.Result); err != nil { + return err + } + c.emit(node, internal.OpReturn, 1) + } + case *internal.CallExpr: + if err := c.Compile(node.Func); err != nil { + return err + } + for _, arg := range node.Args { + if err := c.Compile(arg); err != nil { + return err + } + } + c.emit(node, internal.OpCall, len(node.Args)) + case *internal.ImportExpr: + if node.ModuleName == "" { + return c.errorf(node, "empty module name") + } + + if mod := c.modules.Get(node.ModuleName); mod != nil { + v, err := mod.Import(node.ModuleName) + if err != nil { + return err + } + + switch v := v.(type) { + case []byte: // module written in Tengo + compiled, err := c.compileModule(node, + node.ModuleName, node.ModuleName, v) + if err != nil { + return err + } + c.emit(node, internal.OpConstant, c.addConstant(compiled)) + c.emit(node, internal.OpCall, 0) + case Object: // builtin module + c.emit(node, internal.OpConstant, c.addConstant(v)) + default: + panic(fmt.Errorf("invalid import value type: %T", v)) + } + } else if c.allowFileImport { + moduleName := node.ModuleName + if !strings.HasSuffix(moduleName, ".tengo") { + moduleName += ".tengo" + } + + modulePath, err := filepath.Abs(moduleName) + if err != nil { + return c.errorf(node, "module file path error: %s", + err.Error()) + } + + if err := c.checkCyclicImports(node, modulePath); err != nil { + return err + } + + moduleSrc, err := ioutil.ReadFile(moduleName) + if err != nil { + return c.errorf(node, "module file read error: %s", + err.Error()) + } + + compiled, err := c.compileModule(node, + moduleName, modulePath, moduleSrc) + if err != nil { + return err + } + c.emit(node, internal.OpConstant, c.addConstant(compiled)) + c.emit(node, internal.OpCall, 0) + } else { + return c.errorf(node, "module '%s' not found", node.ModuleName) + } + case *internal.ExportStmt: + // export statement must be in top-level scope + if c.scopeIndex != 0 { + return c.errorf(node, "export not allowed inside function") + } + + // export statement is simply ignore when compiling non-module code + if c.parent == nil { + break + } + if err := c.Compile(node.Result); err != nil { + return err + } + c.emit(node, internal.OpImmutable) + c.emit(node, internal.OpReturn, 1) + case *internal.ErrorExpr: + if err := c.Compile(node.Expr); err != nil { + return err + } + c.emit(node, internal.OpError) + case *internal.ImmutableExpr: + if err := c.Compile(node.Expr); err != nil { + return err + } + c.emit(node, internal.OpImmutable) + case *internal.CondExpr: + if err := c.Compile(node.Cond); err != nil { + return err + } + + // first jump placeholder + jumpPos1 := c.emit(node, internal.OpJumpFalsy, 0) + if err := c.Compile(node.True); err != nil { + return err + } + + // second jump placeholder + jumpPos2 := c.emit(node, internal.OpJump, 0) + + // update first jump offset + curPos := len(c.currentInstructions()) + c.changeOperand(jumpPos1, curPos) + if err := c.Compile(node.False); err != nil { + return err + } + + // update second jump offset + curPos = len(c.currentInstructions()) + c.changeOperand(jumpPos2, curPos) + } + return nil +} + +// Bytecode returns a compiled bytecode. +func (c *Compiler) Bytecode() *Bytecode { + return &Bytecode{ + FileSet: c.file.Set(), + MainFunction: &CompiledFunction{ + Instructions: append(c.currentInstructions(), internal.OpSuspend), + SourceMap: c.currentSourceMap(), + }, + Constants: c.constants, + } +} + +// EnableFileImport enables or disables module loading from local files. +// Local file modules are disabled by default. +func (c *Compiler) EnableFileImport(enable bool) { + c.allowFileImport = enable +} + +func (c *Compiler) compileAssign( + node internal.Node, + lhs, rhs []internal.Expr, + op token.Token, +) error { + numLHS, numRHS := len(lhs), len(rhs) + if numLHS > 1 || numRHS > 1 { + return c.errorf(node, "tuple assignment not allowed") + } + + // resolve and compile left-hand side + ident, selectors := resolveAssignLHS(lhs[0]) + numSel := len(selectors) + + if op == token.Define && numSel > 0 { + // using selector on new variable does not make sense + return c.errorf(node, "operator ':=' not allowed with selector") + } + + symbol, depth, exists := c.symbolTable.Resolve(ident) + if op == token.Define { + if depth == 0 && exists { + return c.errorf(node, "'%s' redeclared in this block", ident) + } + symbol = c.symbolTable.Define(ident) + } else { + if !exists { + return c.errorf(node, "unresolved reference '%s'", ident) + } + } + + // +=, -=, *=, /= + if op != token.Assign && op != token.Define { + if err := c.Compile(lhs[0]); err != nil { + return err + } + } + + // compile RHSs + for _, expr := range rhs { + if err := c.Compile(expr); err != nil { + return err + } + } + + switch op { + case token.AddAssign: + c.emit(node, internal.OpBinaryOp, int(token.Add)) + case token.SubAssign: + c.emit(node, internal.OpBinaryOp, int(token.Sub)) + case token.MulAssign: + c.emit(node, internal.OpBinaryOp, int(token.Mul)) + case token.QuoAssign: + c.emit(node, internal.OpBinaryOp, int(token.Quo)) + case token.RemAssign: + c.emit(node, internal.OpBinaryOp, int(token.Rem)) + case token.AndAssign: + c.emit(node, internal.OpBinaryOp, int(token.And)) + case token.OrAssign: + c.emit(node, internal.OpBinaryOp, int(token.Or)) + case token.AndNotAssign: + c.emit(node, internal.OpBinaryOp, int(token.AndNot)) + case token.XorAssign: + c.emit(node, internal.OpBinaryOp, int(token.Xor)) + case token.ShlAssign: + c.emit(node, internal.OpBinaryOp, int(token.Shl)) + case token.ShrAssign: + c.emit(node, internal.OpBinaryOp, int(token.Shr)) + } + + // compile selector expressions (right to left) + for i := numSel - 1; i >= 0; i-- { + if err := c.Compile(selectors[i]); err != nil { + return err + } + } + + switch symbol.Scope { + case internal.ScopeGlobal: + if numSel > 0 { + c.emit(node, internal.OpSetSelGlobal, symbol.Index, numSel) + } else { + c.emit(node, internal.OpSetGlobal, symbol.Index) + } + case internal.ScopeLocal: + if numSel > 0 { + c.emit(node, internal.OpSetSelLocal, symbol.Index, numSel) + } else { + if op == token.Define && !symbol.LocalAssigned { + c.emit(node, internal.OpDefineLocal, symbol.Index) + } else { + c.emit(node, internal.OpSetLocal, symbol.Index) + } + } + + // mark the symbol as local-assigned + symbol.LocalAssigned = true + case internal.ScopeFree: + if numSel > 0 { + c.emit(node, internal.OpSetSelFree, symbol.Index, numSel) + } else { + c.emit(node, internal.OpSetFree, symbol.Index) + } + default: + panic(fmt.Errorf("invalid assignment variable scope: %s", + symbol.Scope)) + } + return nil +} + +func (c *Compiler) compileLogical(node *internal.BinaryExpr) error { + // left side term + if err := c.Compile(node.LHS); err != nil { + return err + } + + // jump position + var jumpPos int + if node.Token == token.LAnd { + jumpPos = c.emit(node, internal.OpAndJump, 0) + } else { + jumpPos = c.emit(node, internal.OpOrJump, 0) + } + + // right side term + if err := c.Compile(node.RHS); err != nil { + return err + } + + c.changeOperand(jumpPos, len(c.currentInstructions())) + return nil +} + +func (c *Compiler) compileForStmt(stmt *internal.ForStmt) error { + c.symbolTable = c.symbolTable.Fork(true) + defer func() { + c.symbolTable = c.symbolTable.Parent(false) + }() + + // init statement + if stmt.Init != nil { + if err := c.Compile(stmt.Init); err != nil { + return err + } + } + + // pre-condition position + preCondPos := len(c.currentInstructions()) + + // condition expression + postCondPos := -1 + if stmt.Cond != nil { + if err := c.Compile(stmt.Cond); err != nil { + return err + } + // condition jump position + postCondPos = c.emit(stmt, internal.OpJumpFalsy, 0) + } + + // enter loop + loop := c.enterLoop() + + // body statement + if err := c.Compile(stmt.Body); err != nil { + c.leaveLoop() + return err + } + + c.leaveLoop() + + // post-body position + postBodyPos := len(c.currentInstructions()) + + // post statement + if stmt.Post != nil { + if err := c.Compile(stmt.Post); err != nil { + return err + } + } + + // back to condition + c.emit(stmt, internal.OpJump, preCondPos) + + // post-statement position + postStmtPos := len(c.currentInstructions()) + if postCondPos >= 0 { + c.changeOperand(postCondPos, postStmtPos) + } + + // update all break/continue jump positions + for _, pos := range loop.Breaks { + c.changeOperand(pos, postStmtPos) + } + for _, pos := range loop.Continues { + c.changeOperand(pos, postBodyPos) + } + return nil +} + +func (c *Compiler) compileForInStmt(stmt *internal.ForInStmt) error { + c.symbolTable = c.symbolTable.Fork(true) + defer func() { + c.symbolTable = c.symbolTable.Parent(false) + }() + + // for-in statement is compiled like following: + // + // for :it := iterator(iterable); :it.next(); { + // k, v := :it.get() // DEFINE operator + // + // ... body ... + // } + // + // ":it" is a local variable but will be conflict with other user variables + // because character ":" is not allowed. + + // init + // :it = iterator(iterable) + itSymbol := c.symbolTable.Define(":it") + if err := c.Compile(stmt.Iterable); err != nil { + return err + } + c.emit(stmt, internal.OpIteratorInit) + if itSymbol.Scope == internal.ScopeGlobal { + c.emit(stmt, internal.OpSetGlobal, itSymbol.Index) + } else { + c.emit(stmt, internal.OpDefineLocal, itSymbol.Index) + } + + // pre-condition position + preCondPos := len(c.currentInstructions()) + + // condition + // :it.HasMore() + if itSymbol.Scope == internal.ScopeGlobal { + c.emit(stmt, internal.OpGetGlobal, itSymbol.Index) + } else { + c.emit(stmt, internal.OpGetLocal, itSymbol.Index) + } + c.emit(stmt, internal.OpIteratorNext) + + // condition jump position + postCondPos := c.emit(stmt, internal.OpJumpFalsy, 0) + + // enter loop + loop := c.enterLoop() + + // assign key variable + if stmt.Key.Name != "_" { + keySymbol := c.symbolTable.Define(stmt.Key.Name) + if itSymbol.Scope == internal.ScopeGlobal { + c.emit(stmt, internal.OpGetGlobal, itSymbol.Index) + } else { + c.emit(stmt, internal.OpGetLocal, itSymbol.Index) + } + c.emit(stmt, internal.OpIteratorKey) + if keySymbol.Scope == internal.ScopeGlobal { + c.emit(stmt, internal.OpSetGlobal, keySymbol.Index) + } else { + c.emit(stmt, internal.OpDefineLocal, keySymbol.Index) + } + } + + // assign value variable + if stmt.Value.Name != "_" { + valueSymbol := c.symbolTable.Define(stmt.Value.Name) + if itSymbol.Scope == internal.ScopeGlobal { + c.emit(stmt, internal.OpGetGlobal, itSymbol.Index) + } else { + c.emit(stmt, internal.OpGetLocal, itSymbol.Index) + } + c.emit(stmt, internal.OpIteratorValue) + if valueSymbol.Scope == internal.ScopeGlobal { + c.emit(stmt, internal.OpSetGlobal, valueSymbol.Index) + } else { + c.emit(stmt, internal.OpDefineLocal, valueSymbol.Index) + } + } + + // body statement + if err := c.Compile(stmt.Body); err != nil { + c.leaveLoop() + return err + } + + c.leaveLoop() + + // post-body position + postBodyPos := len(c.currentInstructions()) + + // back to condition + c.emit(stmt, internal.OpJump, preCondPos) + + // post-statement position + postStmtPos := len(c.currentInstructions()) + c.changeOperand(postCondPos, postStmtPos) + + // update all break/continue jump positions + for _, pos := range loop.Breaks { + c.changeOperand(pos, postStmtPos) + } + for _, pos := range loop.Continues { + c.changeOperand(pos, postBodyPos) + } + return nil +} + +func (c *Compiler) checkCyclicImports( + node internal.Node, + modulePath string, +) error { + if c.modulePath == modulePath { + return c.errorf(node, "cyclic module import: %s", modulePath) + } else if c.parent != nil { + return c.parent.checkCyclicImports(node, modulePath) + } + return nil +} + +func (c *Compiler) compileModule( + node internal.Node, + moduleName, modulePath string, + src []byte, +) (*CompiledFunction, error) { + if err := c.checkCyclicImports(node, modulePath); err != nil { + return nil, err + } + + compiledModule, exists := c.loadCompiledModule(modulePath) + if exists { + return compiledModule, nil + } + + modFile := c.file.Set().AddFile(moduleName, -1, len(src)) + p := internal.NewParser(modFile, src, nil) + file, err := p.ParseFile() + if err != nil { + return nil, err + } + + // inherit builtin functions + symbolTable := internal.NewSymbolTable() + for _, sym := range c.symbolTable.BuiltinSymbols() { + symbolTable.DefineBuiltin(sym.Index, sym.Name) + } + + // no global scope for the module + symbolTable = symbolTable.Fork(false) + + // compile module + moduleCompiler := c.fork(modFile, modulePath, symbolTable) + if err := moduleCompiler.Compile(file); err != nil { + return nil, err + } + + // code optimization + moduleCompiler.optimizeFunc(node) + compiledFunc := moduleCompiler.Bytecode().MainFunction + compiledFunc.NumLocals = symbolTable.MaxSymbols() + c.storeCompiledModule(modulePath, compiledFunc) + return compiledFunc, nil +} + +func (c *Compiler) loadCompiledModule( + modulePath string, +) (mod *CompiledFunction, ok bool) { + if c.parent != nil { + return c.parent.loadCompiledModule(modulePath) + } + mod, ok = c.compiledModules[modulePath] + return +} + +func (c *Compiler) storeCompiledModule( + modulePath string, + module *CompiledFunction, +) { + if c.parent != nil { + c.parent.storeCompiledModule(modulePath, module) + } + c.compiledModules[modulePath] = module +} + +func (c *Compiler) enterLoop() *internal.Loop { + loop := &internal.Loop{} + c.loops = append(c.loops, loop) + c.loopIndex++ + if c.trace != nil { + c.printTrace("LOOPE", c.loopIndex) + } + return loop +} + +func (c *Compiler) leaveLoop() { + if c.trace != nil { + c.printTrace("LOOPL", c.loopIndex) + } + c.loops = c.loops[:len(c.loops)-1] + c.loopIndex-- +} + +func (c *Compiler) currentLoop() *internal.Loop { + if c.loopIndex >= 0 { + return c.loops[c.loopIndex] + } + return nil +} + +func (c *Compiler) currentInstructions() []byte { + return c.scopes[c.scopeIndex].Instructions +} + +func (c *Compiler) currentSourceMap() map[int]internal.Pos { + return c.scopes[c.scopeIndex].SourceMap +} + +func (c *Compiler) enterScope() { + scope := internal.CompilationScope{ + SymbolInit: make(map[string]bool), + SourceMap: make(map[int]internal.Pos), + } + c.scopes = append(c.scopes, scope) + c.scopeIndex++ + c.symbolTable = c.symbolTable.Fork(false) + if c.trace != nil { + c.printTrace("SCOPE", c.scopeIndex) + } +} + +func (c *Compiler) leaveScope() ( + instructions []byte, + sourceMap map[int]internal.Pos, +) { + instructions = c.currentInstructions() + sourceMap = c.currentSourceMap() + c.scopes = c.scopes[:len(c.scopes)-1] + c.scopeIndex-- + c.symbolTable = c.symbolTable.Parent(true) + if c.trace != nil { + c.printTrace("SCOPL", c.scopeIndex) + } + return +} + +func (c *Compiler) fork( + file *internal.SourceFile, + modulePath string, + symbolTable *internal.SymbolTable, +) *Compiler { + child := NewCompiler(file, symbolTable, nil, c.modules, c.trace) + child.modulePath = modulePath // module file path + child.parent = c // parent to set to current compiler + return child +} + +func (c *Compiler) error(node internal.Node, err error) error { + return &internal.CompilerError{ + FileSet: c.file.Set(), + Node: node, + Err: err, + } +} + +func (c *Compiler) errorf( + node internal.Node, + format string, + args ...interface{}, +) error { + return &internal.CompilerError{ + FileSet: c.file.Set(), + Node: node, + Err: fmt.Errorf(format, args...), + } +} + +func (c *Compiler) addConstant(o Object) int { + if c.parent != nil { + // module compilers will use their parent's constants array + return c.parent.addConstant(o) + } + c.constants = append(c.constants, o) + if c.trace != nil { + c.printTrace(fmt.Sprintf("CONST %04d %s", len(c.constants)-1, o)) + } + return len(c.constants) - 1 +} + +func (c *Compiler) addInstruction(b []byte) int { + posNewIns := len(c.currentInstructions()) + c.scopes[c.scopeIndex].Instructions = append( + c.currentInstructions(), b...) + return posNewIns +} + +func (c *Compiler) replaceInstruction(pos int, inst []byte) { + copy(c.currentInstructions()[pos:], inst) + if c.trace != nil { + c.printTrace(fmt.Sprintf("REPLC %s", + internal.FormatInstructions( + c.scopes[c.scopeIndex].Instructions[pos:], pos)[0])) + } +} + +func (c *Compiler) changeOperand(opPos int, operand ...int) { + op := c.currentInstructions()[opPos] + inst := internal.MakeInstruction(op, operand...) + c.replaceInstruction(opPos, inst) +} + +// optimizeFunc performs some code-level optimization for the current function +// instructions. It also removes unreachable (dead code) instructions and adds +// "returns" instruction if needed. +func (c *Compiler) optimizeFunc(node internal.Node) { + // any instructions between RETURN and the function end + // or instructions between RETURN and jump target position + // are considered as unreachable. + + // pass 1. identify all jump destinations + dsts := make(map[int]bool) + iterateInstructions(c.scopes[c.scopeIndex].Instructions, + func(pos int, opcode internal.Opcode, operands []int) bool { + switch opcode { + case internal.OpJump, internal.OpJumpFalsy, + internal.OpAndJump, internal.OpOrJump: + dsts[operands[0]] = true + } + return true + }) + + // pass 2. eliminate dead code + var newInsts []byte + posMap := make(map[int]int) // old position to new position + var dstIdx int + var deadCode bool + iterateInstructions(c.scopes[c.scopeIndex].Instructions, + func(pos int, opcode internal.Opcode, operands []int) bool { + switch { + case opcode == internal.OpReturn: + if deadCode { + return true + } + deadCode = true + case dsts[pos]: + dstIdx++ + deadCode = false + case deadCode: + return true + } + posMap[pos] = len(newInsts) + newInsts = append(newInsts, + internal.MakeInstruction(opcode, operands...)...) + return true + }) + + // pass 3. update jump positions + var lastOp internal.Opcode + var appendReturn bool + endPos := len(c.scopes[c.scopeIndex].Instructions) + iterateInstructions(newInsts, + func(pos int, opcode internal.Opcode, operands []int) bool { + switch opcode { + case internal.OpJump, internal.OpJumpFalsy, internal.OpAndJump, + internal.OpOrJump: + newDst, ok := posMap[operands[0]] + if ok { + copy(newInsts[pos:], + internal.MakeInstruction(opcode, newDst)) + } else if endPos == operands[0] { + // there's a jump instruction that jumps to the end of + // function compiler should append "return". + appendReturn = true + } else { + panic(fmt.Errorf("invalid jump position: %d", newDst)) + } + } + lastOp = opcode + return true + }) + if lastOp != internal.OpReturn { + appendReturn = true + } + + // pass 4. update source map + newSourceMap := make(map[int]internal.Pos) + for pos, srcPos := range c.scopes[c.scopeIndex].SourceMap { + newPos, ok := posMap[pos] + if ok { + newSourceMap[newPos] = srcPos + } + } + c.scopes[c.scopeIndex].Instructions = newInsts + c.scopes[c.scopeIndex].SourceMap = newSourceMap + + // append "return" + if appendReturn { + c.emit(node, internal.OpReturn, 0) + } +} + +func (c *Compiler) emit( + node internal.Node, + opcode internal.Opcode, + operands ...int, +) int { + filePos := internal.NoPos + if node != nil { + filePos = node.Pos() + } + + inst := internal.MakeInstruction(opcode, operands...) + pos := c.addInstruction(inst) + c.scopes[c.scopeIndex].SourceMap[pos] = filePos + if c.trace != nil { + c.printTrace(fmt.Sprintf("EMIT %s", + internal.FormatInstructions( + c.scopes[c.scopeIndex].Instructions[pos:], pos)[0])) + } + return pos +} + +func (c *Compiler) printTrace(a ...interface{}) { + const ( + dots = ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . " + n = len(dots) + ) + + i := 2 * c.indent + for i > n { + _, _ = fmt.Fprint(c.trace, dots) + i -= n + } + _, _ = fmt.Fprint(c.trace, dots[0:i]) + _, _ = fmt.Fprintln(c.trace, a...) +} + +func resolveAssignLHS( + expr internal.Expr, +) (name string, selectors []internal.Expr) { + switch term := expr.(type) { + case *internal.SelectorExpr: + name, selectors = resolveAssignLHS(term.Expr) + selectors = append(selectors, term.Sel) + return + case *internal.IndexExpr: + name, selectors = resolveAssignLHS(term.Expr) + selectors = append(selectors, term.Index) + case *internal.Ident: + name = term.Name + } + return +} + +func iterateInstructions( + b []byte, + fn func(pos int, opcode internal.Opcode, operands []int) bool, +) { + for i := 0; i < len(b); i++ { + numOperands := internal.OpcodeOperands[b[i]] + operands, read := internal.ReadOperands(numOperands, b[i+1:]) + if !fn(i, b[i], operands) { + break + } + i += read + } +} + +func tracec(c *Compiler, msg string) *Compiler { + c.printTrace(msg, "{") + c.indent++ + return c +} + +func untracec(c *Compiler) { + c.indent-- + c.printTrace("}") +} diff --git a/compiler/ast/array_lit.go b/compiler/ast/array_lit.go deleted file mode 100644 index 9fb4ed6..0000000 --- a/compiler/ast/array_lit.go +++ /dev/null @@ -1,35 +0,0 @@ -package ast - -import ( - "strings" - - "github.com/d5/tengo/compiler/source" -) - -// ArrayLit represents an array literal. -type ArrayLit struct { - Elements []Expr - LBrack source.Pos - RBrack source.Pos -} - -func (e *ArrayLit) exprNode() {} - -// Pos returns the position of first character belonging to the node. -func (e *ArrayLit) Pos() source.Pos { - return e.LBrack -} - -// End returns the position of first character immediately after the node. -func (e *ArrayLit) End() source.Pos { - return e.RBrack + 1 -} - -func (e *ArrayLit) String() string { - var elements []string - for _, m := range e.Elements { - elements = append(elements, m.String()) - } - - return "[" + strings.Join(elements, ", ") + "]" -} diff --git a/compiler/ast/assign_stmt.go b/compiler/ast/assign_stmt.go deleted file mode 100644 index e129114..0000000 --- a/compiler/ast/assign_stmt.go +++ /dev/null @@ -1,40 +0,0 @@ -package ast - -import ( - "strings" - - "github.com/d5/tengo/compiler/source" - "github.com/d5/tengo/compiler/token" -) - -// AssignStmt represents an assignment statement. -type AssignStmt struct { - LHS []Expr - RHS []Expr - Token token.Token - TokenPos source.Pos -} - -func (s *AssignStmt) stmtNode() {} - -// Pos returns the position of first character belonging to the node. -func (s *AssignStmt) Pos() source.Pos { - return s.LHS[0].Pos() -} - -// End returns the position of first character immediately after the node. -func (s *AssignStmt) End() source.Pos { - return s.RHS[len(s.RHS)-1].End() -} - -func (s *AssignStmt) String() string { - var lhs, rhs []string - for _, e := range s.LHS { - lhs = append(lhs, e.String()) - } - for _, e := range s.RHS { - rhs = append(rhs, e.String()) - } - - return strings.Join(lhs, ", ") + " " + s.Token.String() + " " + strings.Join(rhs, ", ") -} diff --git a/compiler/ast/ast.go b/compiler/ast/ast.go deleted file mode 100644 index 9fd0672..0000000 --- a/compiler/ast/ast.go +++ /dev/null @@ -1,5 +0,0 @@ -package ast - -const ( - nullRep = "" -) diff --git a/compiler/ast/bad_expr.go b/compiler/ast/bad_expr.go deleted file mode 100644 index 771f26f..0000000 --- a/compiler/ast/bad_expr.go +++ /dev/null @@ -1,25 +0,0 @@ -package ast - -import "github.com/d5/tengo/compiler/source" - -// BadExpr represents a bad expression. -type BadExpr struct { - From source.Pos - To source.Pos -} - -func (e *BadExpr) exprNode() {} - -// Pos returns the position of first character belonging to the node. -func (e *BadExpr) Pos() source.Pos { - return e.From -} - -// End returns the position of first character immediately after the node. -func (e *BadExpr) End() source.Pos { - return e.To -} - -func (e *BadExpr) String() string { - return "" -} diff --git a/compiler/ast/bad_stmt.go b/compiler/ast/bad_stmt.go deleted file mode 100644 index c2d0ae9..0000000 --- a/compiler/ast/bad_stmt.go +++ /dev/null @@ -1,25 +0,0 @@ -package ast - -import "github.com/d5/tengo/compiler/source" - -// BadStmt represents a bad statement. -type BadStmt struct { - From source.Pos - To source.Pos -} - -func (s *BadStmt) stmtNode() {} - -// Pos returns the position of first character belonging to the node. -func (s *BadStmt) Pos() source.Pos { - return s.From -} - -// End returns the position of first character immediately after the node. -func (s *BadStmt) End() source.Pos { - return s.To -} - -func (s *BadStmt) String() string { - return "" -} diff --git a/compiler/ast/binary_expr.go b/compiler/ast/binary_expr.go deleted file mode 100644 index 0cc5bba..0000000 --- a/compiler/ast/binary_expr.go +++ /dev/null @@ -1,30 +0,0 @@ -package ast - -import ( - "github.com/d5/tengo/compiler/source" - "github.com/d5/tengo/compiler/token" -) - -// BinaryExpr represents a binary operator expression. -type BinaryExpr struct { - LHS Expr - RHS Expr - Token token.Token - TokenPos source.Pos -} - -func (e *BinaryExpr) exprNode() {} - -// Pos returns the position of first character belonging to the node. -func (e *BinaryExpr) Pos() source.Pos { - return e.LHS.Pos() -} - -// End returns the position of first character immediately after the node. -func (e *BinaryExpr) End() source.Pos { - return e.RHS.End() -} - -func (e *BinaryExpr) String() string { - return "(" + e.LHS.String() + " " + e.Token.String() + " " + e.RHS.String() + ")" -} diff --git a/compiler/ast/block_stmt.go b/compiler/ast/block_stmt.go deleted file mode 100644 index 9bde9fa..0000000 --- a/compiler/ast/block_stmt.go +++ /dev/null @@ -1,35 +0,0 @@ -package ast - -import ( - "strings" - - "github.com/d5/tengo/compiler/source" -) - -// BlockStmt represents a block statement. -type BlockStmt struct { - Stmts []Stmt - LBrace source.Pos - RBrace source.Pos -} - -func (s *BlockStmt) stmtNode() {} - -// Pos returns the position of first character belonging to the node. -func (s *BlockStmt) Pos() source.Pos { - return s.LBrace -} - -// End returns the position of first character immediately after the node. -func (s *BlockStmt) End() source.Pos { - return s.RBrace + 1 -} - -func (s *BlockStmt) String() string { - var list []string - for _, e := range s.Stmts { - list = append(list, e.String()) - } - - return "{" + strings.Join(list, "; ") + "}" -} diff --git a/compiler/ast/bool_lit.go b/compiler/ast/bool_lit.go deleted file mode 100644 index e667a5c..0000000 --- a/compiler/ast/bool_lit.go +++ /dev/null @@ -1,26 +0,0 @@ -package ast - -import "github.com/d5/tengo/compiler/source" - -// BoolLit represents a boolean literal. -type BoolLit struct { - Value bool - ValuePos source.Pos - Literal string -} - -func (e *BoolLit) exprNode() {} - -// Pos returns the position of first character belonging to the node. -func (e *BoolLit) Pos() source.Pos { - return e.ValuePos -} - -// End returns the position of first character immediately after the node. -func (e *BoolLit) End() source.Pos { - return source.Pos(int(e.ValuePos) + len(e.Literal)) -} - -func (e *BoolLit) String() string { - return e.Literal -} diff --git a/compiler/ast/branch_stmt.go b/compiler/ast/branch_stmt.go deleted file mode 100644 index f6c7fde..0000000 --- a/compiler/ast/branch_stmt.go +++ /dev/null @@ -1,38 +0,0 @@ -package ast - -import ( - "github.com/d5/tengo/compiler/source" - "github.com/d5/tengo/compiler/token" -) - -// BranchStmt represents a branch statement. -type BranchStmt struct { - Token token.Token - TokenPos source.Pos - Label *Ident -} - -func (s *BranchStmt) stmtNode() {} - -// Pos returns the position of first character belonging to the node. -func (s *BranchStmt) Pos() source.Pos { - return s.TokenPos -} - -// End returns the position of first character immediately after the node. -func (s *BranchStmt) End() source.Pos { - if s.Label != nil { - return s.Label.End() - } - - return source.Pos(int(s.TokenPos) + len(s.Token.String())) -} - -func (s *BranchStmt) String() string { - var label string - if s.Label != nil { - label = " " + s.Label.Name - } - - return s.Token.String() + label -} diff --git a/compiler/ast/call_expr.go b/compiler/ast/call_expr.go deleted file mode 100644 index 0219d7c..0000000 --- a/compiler/ast/call_expr.go +++ /dev/null @@ -1,36 +0,0 @@ -package ast - -import ( - "strings" - - "github.com/d5/tengo/compiler/source" -) - -// CallExpr represents a function call expression. -type CallExpr struct { - Func Expr - LParen source.Pos - Args []Expr - RParen source.Pos -} - -func (e *CallExpr) exprNode() {} - -// Pos returns the position of first character belonging to the node. -func (e *CallExpr) Pos() source.Pos { - return e.Func.Pos() -} - -// End returns the position of first character immediately after the node. -func (e *CallExpr) End() source.Pos { - return e.RParen + 1 -} - -func (e *CallExpr) String() string { - var args []string - for _, e := range e.Args { - args = append(args, e.String()) - } - - return e.Func.String() + "(" + strings.Join(args, ", ") + ")" -} diff --git a/compiler/ast/char_lit.go b/compiler/ast/char_lit.go deleted file mode 100644 index 592f874..0000000 --- a/compiler/ast/char_lit.go +++ /dev/null @@ -1,26 +0,0 @@ -package ast - -import "github.com/d5/tengo/compiler/source" - -// CharLit represents a character literal. -type CharLit struct { - Value rune - ValuePos source.Pos - Literal string -} - -func (e *CharLit) exprNode() {} - -// Pos returns the position of first character belonging to the node. -func (e *CharLit) Pos() source.Pos { - return e.ValuePos -} - -// End returns the position of first character immediately after the node. -func (e *CharLit) End() source.Pos { - return source.Pos(int(e.ValuePos) + len(e.Literal)) -} - -func (e *CharLit) String() string { - return e.Literal -} diff --git a/compiler/ast/cond_expr.go b/compiler/ast/cond_expr.go deleted file mode 100644 index bb1db84..0000000 --- a/compiler/ast/cond_expr.go +++ /dev/null @@ -1,30 +0,0 @@ -package ast - -import ( - "github.com/d5/tengo/compiler/source" -) - -// CondExpr represents a ternary conditional expression. -type CondExpr struct { - Cond Expr - True Expr - False Expr - QuestionPos source.Pos - ColonPos source.Pos -} - -func (e *CondExpr) exprNode() {} - -// Pos returns the position of first character belonging to the node. -func (e *CondExpr) Pos() source.Pos { - return e.Cond.Pos() -} - -// End returns the position of first character immediately after the node. -func (e *CondExpr) End() source.Pos { - return e.False.End() -} - -func (e *CondExpr) String() string { - return "(" + e.Cond.String() + " ? " + e.True.String() + " : " + e.False.String() + ")" -} diff --git a/compiler/ast/empty_stmt.go b/compiler/ast/empty_stmt.go deleted file mode 100644 index a2ac6ff..0000000 --- a/compiler/ast/empty_stmt.go +++ /dev/null @@ -1,29 +0,0 @@ -package ast - -import "github.com/d5/tengo/compiler/source" - -// EmptyStmt represents an empty statement. -type EmptyStmt struct { - Semicolon source.Pos - Implicit bool -} - -func (s *EmptyStmt) stmtNode() {} - -// Pos returns the position of first character belonging to the node. -func (s *EmptyStmt) Pos() source.Pos { - return s.Semicolon -} - -// End returns the position of first character immediately after the node. -func (s *EmptyStmt) End() source.Pos { - if s.Implicit { - return s.Semicolon - } - - return s.Semicolon + 1 -} - -func (s *EmptyStmt) String() string { - return ";" -} diff --git a/compiler/ast/error_expr.go b/compiler/ast/error_expr.go deleted file mode 100644 index 7ce5667..0000000 --- a/compiler/ast/error_expr.go +++ /dev/null @@ -1,29 +0,0 @@ -package ast - -import ( - "github.com/d5/tengo/compiler/source" -) - -// ErrorExpr represents an error expression -type ErrorExpr struct { - Expr Expr - ErrorPos source.Pos - LParen source.Pos - RParen source.Pos -} - -func (e *ErrorExpr) exprNode() {} - -// Pos returns the position of first character belonging to the node. -func (e *ErrorExpr) Pos() source.Pos { - return e.ErrorPos -} - -// End returns the position of first character immediately after the node. -func (e *ErrorExpr) End() source.Pos { - return e.RParen -} - -func (e *ErrorExpr) String() string { - return "error(" + e.Expr.String() + ")" -} diff --git a/compiler/ast/export_stmt.go b/compiler/ast/export_stmt.go deleted file mode 100644 index 64eb760..0000000 --- a/compiler/ast/export_stmt.go +++ /dev/null @@ -1,27 +0,0 @@ -package ast - -import ( - "github.com/d5/tengo/compiler/source" -) - -// ExportStmt represents an export statement. -type ExportStmt struct { - ExportPos source.Pos - Result Expr -} - -func (s *ExportStmt) stmtNode() {} - -// Pos returns the position of first character belonging to the node. -func (s *ExportStmt) Pos() source.Pos { - return s.ExportPos -} - -// End returns the position of first character immediately after the node. -func (s *ExportStmt) End() source.Pos { - return s.Result.End() -} - -func (s *ExportStmt) String() string { - return "export " + s.Result.String() -} diff --git a/compiler/ast/expr.go b/compiler/ast/expr.go deleted file mode 100644 index 764bace..0000000 --- a/compiler/ast/expr.go +++ /dev/null @@ -1,7 +0,0 @@ -package ast - -// Expr represents an expression node in the AST. -type Expr interface { - Node - exprNode() -} diff --git a/compiler/ast/expr_stmt.go b/compiler/ast/expr_stmt.go deleted file mode 100644 index 095a3ad..0000000 --- a/compiler/ast/expr_stmt.go +++ /dev/null @@ -1,24 +0,0 @@ -package ast - -import "github.com/d5/tengo/compiler/source" - -// ExprStmt represents an expression statement. -type ExprStmt struct { - Expr Expr -} - -func (s *ExprStmt) stmtNode() {} - -// Pos returns the position of first character belonging to the node. -func (s *ExprStmt) Pos() source.Pos { - return s.Expr.Pos() -} - -// End returns the position of first character immediately after the node. -func (s *ExprStmt) End() source.Pos { - return s.Expr.End() -} - -func (s *ExprStmt) String() string { - return s.Expr.String() -} diff --git a/compiler/ast/float_lit.go b/compiler/ast/float_lit.go deleted file mode 100644 index 670f744..0000000 --- a/compiler/ast/float_lit.go +++ /dev/null @@ -1,26 +0,0 @@ -package ast - -import "github.com/d5/tengo/compiler/source" - -// FloatLit represents a floating point literal. -type FloatLit struct { - Value float64 - ValuePos source.Pos - Literal string -} - -func (e *FloatLit) exprNode() {} - -// Pos returns the position of first character belonging to the node. -func (e *FloatLit) Pos() source.Pos { - return e.ValuePos -} - -// End returns the position of first character immediately after the node. -func (e *FloatLit) End() source.Pos { - return source.Pos(int(e.ValuePos) + len(e.Literal)) -} - -func (e *FloatLit) String() string { - return e.Literal -} diff --git a/compiler/ast/for_in_stmt.go b/compiler/ast/for_in_stmt.go deleted file mode 100644 index 18020b5..0000000 --- a/compiler/ast/for_in_stmt.go +++ /dev/null @@ -1,32 +0,0 @@ -package ast - -import "github.com/d5/tengo/compiler/source" - -// ForInStmt represents a for-in statement. -type ForInStmt struct { - ForPos source.Pos - Key *Ident - Value *Ident - Iterable Expr - Body *BlockStmt -} - -func (s *ForInStmt) stmtNode() {} - -// Pos returns the position of first character belonging to the node. -func (s *ForInStmt) Pos() source.Pos { - return s.ForPos -} - -// End returns the position of first character immediately after the node. -func (s *ForInStmt) End() source.Pos { - return s.Body.End() -} - -func (s *ForInStmt) String() string { - if s.Value != nil { - return "for " + s.Key.String() + ", " + s.Value.String() + " in " + s.Iterable.String() + " " + s.Body.String() - } - - return "for " + s.Key.String() + " in " + s.Iterable.String() + " " + s.Body.String() -} diff --git a/compiler/ast/for_stmt.go b/compiler/ast/for_stmt.go deleted file mode 100644 index 4b5a0a1..0000000 --- a/compiler/ast/for_stmt.go +++ /dev/null @@ -1,43 +0,0 @@ -package ast - -import "github.com/d5/tengo/compiler/source" - -// ForStmt represents a for statement. -type ForStmt struct { - ForPos source.Pos - Init Stmt - Cond Expr - Post Stmt - Body *BlockStmt -} - -func (s *ForStmt) stmtNode() {} - -// Pos returns the position of first character belonging to the node. -func (s *ForStmt) Pos() source.Pos { - return s.ForPos -} - -// End returns the position of first character immediately after the node. -func (s *ForStmt) End() source.Pos { - return s.Body.End() -} - -func (s *ForStmt) String() string { - var init, cond, post string - if s.Init != nil { - init = s.Init.String() - } - if s.Cond != nil { - cond = s.Cond.String() + " " - } - if s.Post != nil { - post = s.Post.String() - } - - if init != "" || post != "" { - return "for " + init + " ; " + cond + " ; " + post + s.Body.String() - } - - return "for " + cond + s.Body.String() -} diff --git a/compiler/ast/func_lit.go b/compiler/ast/func_lit.go deleted file mode 100644 index 2e90ed2..0000000 --- a/compiler/ast/func_lit.go +++ /dev/null @@ -1,25 +0,0 @@ -package ast - -import "github.com/d5/tengo/compiler/source" - -// FuncLit represents a function literal. -type FuncLit struct { - Type *FuncType - Body *BlockStmt -} - -func (e *FuncLit) exprNode() {} - -// Pos returns the position of first character belonging to the node. -func (e *FuncLit) Pos() source.Pos { - return e.Type.Pos() -} - -// End returns the position of first character immediately after the node. -func (e *FuncLit) End() source.Pos { - return e.Body.End() -} - -func (e *FuncLit) String() string { - return "func" + e.Type.Params.String() + " " + e.Body.String() -} diff --git a/compiler/ast/func_type.go b/compiler/ast/func_type.go deleted file mode 100644 index 2afaabb..0000000 --- a/compiler/ast/func_type.go +++ /dev/null @@ -1,25 +0,0 @@ -package ast - -import "github.com/d5/tengo/compiler/source" - -// FuncType represents a function type definition. -type FuncType struct { - FuncPos source.Pos - Params *IdentList -} - -func (e *FuncType) exprNode() {} - -// Pos returns the position of first character belonging to the node. -func (e *FuncType) Pos() source.Pos { - return e.FuncPos -} - -// End returns the position of first character immediately after the node. -func (e *FuncType) End() source.Pos { - return e.Params.End() -} - -func (e *FuncType) String() string { - return "func" + e.Params.String() -} diff --git a/compiler/ast/ident.go b/compiler/ast/ident.go deleted file mode 100644 index 33b7ff7..0000000 --- a/compiler/ast/ident.go +++ /dev/null @@ -1,29 +0,0 @@ -package ast - -import "github.com/d5/tengo/compiler/source" - -// Ident represents an identifier. -type Ident struct { - Name string - NamePos source.Pos -} - -func (e *Ident) exprNode() {} - -// Pos returns the position of first character belonging to the node. -func (e *Ident) Pos() source.Pos { - return e.NamePos -} - -// End returns the position of first character immediately after the node. -func (e *Ident) End() source.Pos { - return source.Pos(int(e.NamePos) + len(e.Name)) -} - -func (e *Ident) String() string { - if e != nil { - return e.Name - } - - return nullRep -} diff --git a/compiler/ast/if_stmt.go b/compiler/ast/if_stmt.go deleted file mode 100644 index b3d6560..0000000 --- a/compiler/ast/if_stmt.go +++ /dev/null @@ -1,40 +0,0 @@ -package ast - -import "github.com/d5/tengo/compiler/source" - -// IfStmt represents an if statement. -type IfStmt struct { - IfPos source.Pos - Init Stmt - Cond Expr - Body *BlockStmt - Else Stmt // else branch; or nil -} - -func (s *IfStmt) stmtNode() {} - -// Pos returns the position of first character belonging to the node. -func (s *IfStmt) Pos() source.Pos { - return s.IfPos -} - -// End returns the position of first character immediately after the node. -func (s *IfStmt) End() source.Pos { - if s.Else != nil { - return s.Else.End() - } - - return s.Body.End() -} - -func (s *IfStmt) String() string { - var initStmt, elseStmt string - if s.Init != nil { - initStmt = s.Init.String() + "; " - } - if s.Else != nil { - elseStmt = " else " + s.Else.String() - } - - return "if " + initStmt + s.Cond.String() + " " + s.Body.String() + elseStmt -} diff --git a/compiler/ast/immutable_expr.go b/compiler/ast/immutable_expr.go deleted file mode 100644 index f9843b5..0000000 --- a/compiler/ast/immutable_expr.go +++ /dev/null @@ -1,29 +0,0 @@ -package ast - -import ( - "github.com/d5/tengo/compiler/source" -) - -// ImmutableExpr represents an immutable expression -type ImmutableExpr struct { - Expr Expr - ErrorPos source.Pos - LParen source.Pos - RParen source.Pos -} - -func (e *ImmutableExpr) exprNode() {} - -// Pos returns the position of first character belonging to the node. -func (e *ImmutableExpr) Pos() source.Pos { - return e.ErrorPos -} - -// End returns the position of first character immediately after the node. -func (e *ImmutableExpr) End() source.Pos { - return e.RParen -} - -func (e *ImmutableExpr) String() string { - return "immutable(" + e.Expr.String() + ")" -} diff --git a/compiler/ast/import_expr.go b/compiler/ast/import_expr.go deleted file mode 100644 index 6eff74a..0000000 --- a/compiler/ast/import_expr.go +++ /dev/null @@ -1,29 +0,0 @@ -package ast - -import ( - "github.com/d5/tengo/compiler/source" - "github.com/d5/tengo/compiler/token" -) - -// ImportExpr represents an import expression -type ImportExpr struct { - ModuleName string - Token token.Token - TokenPos source.Pos -} - -func (e *ImportExpr) exprNode() {} - -// Pos returns the position of first character belonging to the node. -func (e *ImportExpr) Pos() source.Pos { - return e.TokenPos -} - -// End returns the position of first character immediately after the node. -func (e *ImportExpr) End() source.Pos { - return source.Pos(int(e.TokenPos) + 10 + len(e.ModuleName)) // import("moduleName") -} - -func (e *ImportExpr) String() string { - return `import("` + e.ModuleName + `")"` -} diff --git a/compiler/ast/inc_dec_stmt.go b/compiler/ast/inc_dec_stmt.go deleted file mode 100644 index e4e7f92..0000000 --- a/compiler/ast/inc_dec_stmt.go +++ /dev/null @@ -1,29 +0,0 @@ -package ast - -import ( - "github.com/d5/tengo/compiler/source" - "github.com/d5/tengo/compiler/token" -) - -// IncDecStmt represents increment or decrement statement. -type IncDecStmt struct { - Expr Expr - Token token.Token - TokenPos source.Pos -} - -func (s *IncDecStmt) stmtNode() {} - -// Pos returns the position of first character belonging to the node. -func (s *IncDecStmt) Pos() source.Pos { - return s.Expr.Pos() -} - -// End returns the position of first character immediately after the node. -func (s *IncDecStmt) End() source.Pos { - return source.Pos(int(s.TokenPos) + 2) -} - -func (s *IncDecStmt) String() string { - return s.Expr.String() + s.Token.String() -} diff --git a/compiler/ast/index_expr.go b/compiler/ast/index_expr.go deleted file mode 100644 index bc0992a..0000000 --- a/compiler/ast/index_expr.go +++ /dev/null @@ -1,32 +0,0 @@ -package ast - -import "github.com/d5/tengo/compiler/source" - -// IndexExpr represents an index expression. -type IndexExpr struct { - Expr Expr - LBrack source.Pos - Index Expr - RBrack source.Pos -} - -func (e *IndexExpr) exprNode() {} - -// Pos returns the position of first character belonging to the node. -func (e *IndexExpr) Pos() source.Pos { - return e.Expr.Pos() -} - -// End returns the position of first character immediately after the node. -func (e *IndexExpr) End() source.Pos { - return e.RBrack + 1 -} - -func (e *IndexExpr) String() string { - var index string - if e.Index != nil { - index = e.Index.String() - } - - return e.Expr.String() + "[" + index + "]" -} diff --git a/compiler/ast/int_lit.go b/compiler/ast/int_lit.go deleted file mode 100644 index 3e1fd98..0000000 --- a/compiler/ast/int_lit.go +++ /dev/null @@ -1,26 +0,0 @@ -package ast - -import "github.com/d5/tengo/compiler/source" - -// IntLit represents an integer literal. -type IntLit struct { - Value int64 - ValuePos source.Pos - Literal string -} - -func (e *IntLit) exprNode() {} - -// Pos returns the position of first character belonging to the node. -func (e *IntLit) Pos() source.Pos { - return e.ValuePos -} - -// End returns the position of first character immediately after the node. -func (e *IntLit) End() source.Pos { - return source.Pos(int(e.ValuePos) + len(e.Literal)) -} - -func (e *IntLit) String() string { - return e.Literal -} diff --git a/compiler/ast/map_element_lit.go b/compiler/ast/map_element_lit.go deleted file mode 100644 index 3d7fca9..0000000 --- a/compiler/ast/map_element_lit.go +++ /dev/null @@ -1,27 +0,0 @@ -package ast - -import "github.com/d5/tengo/compiler/source" - -// MapElementLit represents a map element. -type MapElementLit struct { - Key string - KeyPos source.Pos - ColonPos source.Pos - Value Expr -} - -func (e *MapElementLit) exprNode() {} - -// Pos returns the position of first character belonging to the node. -func (e *MapElementLit) Pos() source.Pos { - return e.KeyPos -} - -// End returns the position of first character immediately after the node. -func (e *MapElementLit) End() source.Pos { - return e.Value.End() -} - -func (e *MapElementLit) String() string { - return e.Key + ": " + e.Value.String() -} diff --git a/compiler/ast/map_lit.go b/compiler/ast/map_lit.go deleted file mode 100644 index a228224..0000000 --- a/compiler/ast/map_lit.go +++ /dev/null @@ -1,35 +0,0 @@ -package ast - -import ( - "strings" - - "github.com/d5/tengo/compiler/source" -) - -// MapLit represents a map literal. -type MapLit struct { - LBrace source.Pos - Elements []*MapElementLit - RBrace source.Pos -} - -func (e *MapLit) exprNode() {} - -// Pos returns the position of first character belonging to the node. -func (e *MapLit) Pos() source.Pos { - return e.LBrace -} - -// End returns the position of first character immediately after the node. -func (e *MapLit) End() source.Pos { - return e.RBrace + 1 -} - -func (e *MapLit) String() string { - var elements []string - for _, m := range e.Elements { - elements = append(elements, m.String()) - } - - return "{" + strings.Join(elements, ", ") + "}" -} diff --git a/compiler/ast/node.go b/compiler/ast/node.go deleted file mode 100644 index 44677b4..0000000 --- a/compiler/ast/node.go +++ /dev/null @@ -1,13 +0,0 @@ -package ast - -import "github.com/d5/tengo/compiler/source" - -// Node represents a node in the AST. -type Node interface { - // Pos returns the position of first character belonging to the node. - Pos() source.Pos - // End returns the position of first character immediately after the node. - End() source.Pos - // String returns a string representation of the node. - String() string -} diff --git a/compiler/ast/paren_expr.go b/compiler/ast/paren_expr.go deleted file mode 100644 index 8db4ac0..0000000 --- a/compiler/ast/paren_expr.go +++ /dev/null @@ -1,26 +0,0 @@ -package ast - -import "github.com/d5/tengo/compiler/source" - -// ParenExpr represents a parenthesis wrapped expression. -type ParenExpr struct { - Expr Expr - LParen source.Pos - RParen source.Pos -} - -func (e *ParenExpr) exprNode() {} - -// Pos returns the position of first character belonging to the node. -func (e *ParenExpr) Pos() source.Pos { - return e.LParen -} - -// End returns the position of first character immediately after the node. -func (e *ParenExpr) End() source.Pos { - return e.RParen + 1 -} - -func (e *ParenExpr) String() string { - return "(" + e.Expr.String() + ")" -} diff --git a/compiler/ast/return_stmt.go b/compiler/ast/return_stmt.go deleted file mode 100644 index 592d45b..0000000 --- a/compiler/ast/return_stmt.go +++ /dev/null @@ -1,35 +0,0 @@ -package ast - -import ( - "github.com/d5/tengo/compiler/source" -) - -// ReturnStmt represents a return statement. -type ReturnStmt struct { - ReturnPos source.Pos - Result Expr -} - -func (s *ReturnStmt) stmtNode() {} - -// Pos returns the position of first character belonging to the node. -func (s *ReturnStmt) Pos() source.Pos { - return s.ReturnPos -} - -// End returns the position of first character immediately after the node. -func (s *ReturnStmt) End() source.Pos { - if s.Result != nil { - return s.Result.End() - } - - return s.ReturnPos + 6 -} - -func (s *ReturnStmt) String() string { - if s.Result != nil { - return "return " + s.Result.String() - } - - return "return" -} diff --git a/compiler/ast/selector_expr.go b/compiler/ast/selector_expr.go deleted file mode 100644 index 31d2e6d..0000000 --- a/compiler/ast/selector_expr.go +++ /dev/null @@ -1,25 +0,0 @@ -package ast - -import "github.com/d5/tengo/compiler/source" - -// SelectorExpr represents a selector expression. -type SelectorExpr struct { - Expr Expr - Sel Expr -} - -func (e *SelectorExpr) exprNode() {} - -// Pos returns the position of first character belonging to the node. -func (e *SelectorExpr) Pos() source.Pos { - return e.Expr.Pos() -} - -// End returns the position of first character immediately after the node. -func (e *SelectorExpr) End() source.Pos { - return e.Sel.End() -} - -func (e *SelectorExpr) String() string { - return e.Expr.String() + "." + e.Sel.String() -} diff --git a/compiler/ast/slice_expr.go b/compiler/ast/slice_expr.go deleted file mode 100644 index e7e2e05..0000000 --- a/compiler/ast/slice_expr.go +++ /dev/null @@ -1,36 +0,0 @@ -package ast - -import "github.com/d5/tengo/compiler/source" - -// SliceExpr represents a slice expression. -type SliceExpr struct { - Expr Expr - LBrack source.Pos - Low Expr - High Expr - RBrack source.Pos -} - -func (e *SliceExpr) exprNode() {} - -// Pos returns the position of first character belonging to the node. -func (e *SliceExpr) Pos() source.Pos { - return e.Expr.Pos() -} - -// End returns the position of first character immediately after the node. -func (e *SliceExpr) End() source.Pos { - return e.RBrack + 1 -} - -func (e *SliceExpr) String() string { - var low, high string - if e.Low != nil { - low = e.Low.String() - } - if e.High != nil { - high = e.High.String() - } - - return e.Expr.String() + "[" + low + ":" + high + "]" -} diff --git a/compiler/ast/stmt.go b/compiler/ast/stmt.go deleted file mode 100644 index 6b26ba8..0000000 --- a/compiler/ast/stmt.go +++ /dev/null @@ -1,7 +0,0 @@ -package ast - -// Stmt represents a statement in the AST. -type Stmt interface { - Node - stmtNode() -} diff --git a/compiler/ast/string_lit.go b/compiler/ast/string_lit.go deleted file mode 100644 index 2119d34..0000000 --- a/compiler/ast/string_lit.go +++ /dev/null @@ -1,26 +0,0 @@ -package ast - -import "github.com/d5/tengo/compiler/source" - -// StringLit represents a string literal. -type StringLit struct { - Value string - ValuePos source.Pos - Literal string -} - -func (e *StringLit) exprNode() {} - -// Pos returns the position of first character belonging to the node. -func (e *StringLit) Pos() source.Pos { - return e.ValuePos -} - -// End returns the position of first character immediately after the node. -func (e *StringLit) End() source.Pos { - return source.Pos(int(e.ValuePos) + len(e.Literal)) -} - -func (e *StringLit) String() string { - return e.Literal -} diff --git a/compiler/ast/unary_expr.go b/compiler/ast/unary_expr.go deleted file mode 100644 index 5323614..0000000 --- a/compiler/ast/unary_expr.go +++ /dev/null @@ -1,29 +0,0 @@ -package ast - -import ( - "github.com/d5/tengo/compiler/source" - "github.com/d5/tengo/compiler/token" -) - -// UnaryExpr represents an unary operator expression. -type UnaryExpr struct { - Expr Expr - Token token.Token - TokenPos source.Pos -} - -func (e *UnaryExpr) exprNode() {} - -// Pos returns the position of first character belonging to the node. -func (e *UnaryExpr) Pos() source.Pos { - return e.Expr.Pos() -} - -// End returns the position of first character immediately after the node. -func (e *UnaryExpr) End() source.Pos { - return e.Expr.End() -} - -func (e *UnaryExpr) String() string { - return "(" + e.Token.String() + e.Expr.String() + ")" -} diff --git a/compiler/ast/undefined_lit.go b/compiler/ast/undefined_lit.go deleted file mode 100644 index 8e51b11..0000000 --- a/compiler/ast/undefined_lit.go +++ /dev/null @@ -1,24 +0,0 @@ -package ast - -import "github.com/d5/tengo/compiler/source" - -// UndefinedLit represents an undefined literal. -type UndefinedLit struct { - TokenPos source.Pos -} - -func (e *UndefinedLit) exprNode() {} - -// Pos returns the position of first character belonging to the node. -func (e *UndefinedLit) Pos() source.Pos { - return e.TokenPos -} - -// End returns the position of first character immediately after the node. -func (e *UndefinedLit) End() source.Pos { - return e.TokenPos + 9 // len(undefined) == 9 -} - -func (e *UndefinedLit) String() string { - return "undefined" -} diff --git a/compiler/bytecode.go b/compiler/bytecode.go deleted file mode 100644 index 35f36c0..0000000 --- a/compiler/bytecode.go +++ /dev/null @@ -1,90 +0,0 @@ -package compiler - -import ( - "encoding/gob" - "fmt" - "io" - "reflect" - - "github.com/d5/tengo/compiler/source" - "github.com/d5/tengo/objects" -) - -// Bytecode is a compiled instructions and constants. -type Bytecode struct { - FileSet *source.FileSet - MainFunction *objects.CompiledFunction - Constants []objects.Object -} - -// Encode writes Bytecode data to the writer. -func (b *Bytecode) Encode(w io.Writer) error { - enc := gob.NewEncoder(w) - - if err := enc.Encode(b.FileSet); err != nil { - return err - } - - if err := enc.Encode(b.MainFunction); err != nil { - return err - } - - // constants - return enc.Encode(b.Constants) -} - -// CountObjects returns the number of objects found in Constants. -func (b *Bytecode) CountObjects() int { - n := 0 - - for _, c := range b.Constants { - n += objects.CountObjects(c) - } - - return n -} - -// FormatInstructions returns human readable string representations of -// compiled instructions. -func (b *Bytecode) FormatInstructions() []string { - return FormatInstructions(b.MainFunction.Instructions, 0) -} - -// FormatConstants returns human readable string representations of -// compiled constants. -func (b *Bytecode) FormatConstants() (output []string) { - for cidx, cn := range b.Constants { - switch cn := cn.(type) { - case *objects.CompiledFunction: - output = append(output, fmt.Sprintf("[% 3d] (Compiled Function|%p)", cidx, &cn)) - for _, l := range FormatInstructions(cn.Instructions, 0) { - output = append(output, fmt.Sprintf(" %s", l)) - } - default: - output = append(output, fmt.Sprintf("[% 3d] %s (%s|%p)", cidx, cn, reflect.TypeOf(cn).Elem().Name(), &cn)) - } - } - - return -} - -func init() { - gob.Register(&source.FileSet{}) - gob.Register(&source.File{}) - gob.Register(&objects.Array{}) - gob.Register(&objects.Bool{}) - gob.Register(&objects.Bytes{}) - gob.Register(&objects.Char{}) - gob.Register(&objects.Closure{}) - gob.Register(&objects.CompiledFunction{}) - gob.Register(&objects.Error{}) - gob.Register(&objects.Float{}) - gob.Register(&objects.ImmutableArray{}) - gob.Register(&objects.ImmutableMap{}) - gob.Register(&objects.Int{}) - gob.Register(&objects.Map{}) - gob.Register(&objects.String{}) - gob.Register(&objects.Time{}) - gob.Register(&objects.Undefined{}) - gob.Register(&objects.UserFunction{}) -} diff --git a/compiler/bytecode_decode.go b/compiler/bytecode_decode.go deleted file mode 100644 index b3b32a6..0000000 --- a/compiler/bytecode_decode.go +++ /dev/null @@ -1,97 +0,0 @@ -package compiler - -import ( - "encoding/gob" - "fmt" - "io" - - "github.com/d5/tengo/objects" -) - -// Decode reads Bytecode data from the reader. -func (b *Bytecode) Decode(r io.Reader, modules *objects.ModuleMap) error { - if modules == nil { - modules = objects.NewModuleMap() - } - - dec := gob.NewDecoder(r) - - if err := dec.Decode(&b.FileSet); err != nil { - return err - } - // TODO: files in b.FileSet.File does not have their 'set' field properly set to b.FileSet - // as it's private field and not serialized by gob encoder/decoder. - - if err := dec.Decode(&b.MainFunction); err != nil { - return err - } - - if err := dec.Decode(&b.Constants); err != nil { - return err - } - for i, v := range b.Constants { - fv, err := fixDecoded(v, modules) - if err != nil { - return err - } - b.Constants[i] = fv - } - - return nil -} - -func fixDecoded(o objects.Object, modules *objects.ModuleMap) (objects.Object, error) { - switch o := o.(type) { - case *objects.Bool: - if o.IsFalsy() { - return objects.FalseValue, nil - } - return objects.TrueValue, nil - case *objects.Undefined: - return objects.UndefinedValue, nil - case *objects.Array: - for i, v := range o.Value { - fv, err := fixDecoded(v, modules) - if err != nil { - return nil, err - } - o.Value[i] = fv - } - case *objects.ImmutableArray: - for i, v := range o.Value { - fv, err := fixDecoded(v, modules) - if err != nil { - return nil, err - } - o.Value[i] = fv - } - case *objects.Map: - for k, v := range o.Value { - fv, err := fixDecoded(v, modules) - if err != nil { - return nil, err - } - o.Value[k] = fv - } - case *objects.ImmutableMap: - modName := moduleName(o) - if mod := modules.GetBuiltinModule(modName); mod != nil { - return mod.AsImmutableMap(modName), nil - } - - for k, v := range o.Value { - // encoding of user function not supported - if _, isUserFunction := v.(*objects.UserFunction); isUserFunction { - return nil, fmt.Errorf("user function not decodable") - } - - fv, err := fixDecoded(v, modules) - if err != nil { - return nil, err - } - o.Value[k] = fv - } - } - - return o, nil -} diff --git a/compiler/bytecode_optimize.go b/compiler/bytecode_optimize.go deleted file mode 100644 index 9526de2..0000000 --- a/compiler/bytecode_optimize.go +++ /dev/null @@ -1,129 +0,0 @@ -package compiler - -import ( - "fmt" - - "github.com/d5/tengo/objects" -) - -// RemoveDuplicates finds and remove the duplicate values in Constants. -// Note this function mutates Bytecode. -func (b *Bytecode) RemoveDuplicates() { - var deduped []objects.Object - - indexMap := make(map[int]int) // mapping from old constant index to new index - ints := make(map[int64]int) - strings := make(map[string]int) - floats := make(map[float64]int) - chars := make(map[rune]int) - immutableMaps := make(map[string]int) // for modules - - for curIdx, c := range b.Constants { - switch c := c.(type) { - case *objects.CompiledFunction: - // add to deduped list - indexMap[curIdx] = len(deduped) - deduped = append(deduped, c) - case *objects.ImmutableMap: - modName := moduleName(c) - newIdx, ok := immutableMaps[modName] - if modName != "" && ok { - indexMap[curIdx] = newIdx - } else { - newIdx = len(deduped) - immutableMaps[modName] = newIdx - indexMap[curIdx] = newIdx - deduped = append(deduped, c) - } - case *objects.Int: - if newIdx, ok := ints[c.Value]; ok { - indexMap[curIdx] = newIdx - } else { - newIdx = len(deduped) - ints[c.Value] = newIdx - indexMap[curIdx] = newIdx - deduped = append(deduped, c) - } - case *objects.String: - if newIdx, ok := strings[c.Value]; ok { - indexMap[curIdx] = newIdx - } else { - newIdx = len(deduped) - strings[c.Value] = newIdx - indexMap[curIdx] = newIdx - deduped = append(deduped, c) - } - case *objects.Float: - if newIdx, ok := floats[c.Value]; ok { - indexMap[curIdx] = newIdx - } else { - newIdx = len(deduped) - floats[c.Value] = newIdx - indexMap[curIdx] = newIdx - deduped = append(deduped, c) - } - case *objects.Char: - if newIdx, ok := chars[c.Value]; ok { - indexMap[curIdx] = newIdx - } else { - newIdx = len(deduped) - chars[c.Value] = newIdx - indexMap[curIdx] = newIdx - deduped = append(deduped, c) - } - default: - panic(fmt.Errorf("unsupported top-level constant type: %s", c.TypeName())) - } - } - - // replace with de-duplicated constants - b.Constants = deduped - - // update CONST instructions with new indexes - // main function - updateConstIndexes(b.MainFunction.Instructions, indexMap) - // other compiled functions in constants - for _, c := range b.Constants { - switch c := c.(type) { - case *objects.CompiledFunction: - updateConstIndexes(c.Instructions, indexMap) - } - } -} - -func updateConstIndexes(insts []byte, indexMap map[int]int) { - i := 0 - for i < len(insts) { - op := insts[i] - numOperands := OpcodeOperands[op] - _, read := ReadOperands(numOperands, insts[i+1:]) - - switch op { - case OpConstant: - curIdx := int(insts[i+2]) | int(insts[i+1])<<8 - newIdx, ok := indexMap[curIdx] - if !ok { - panic(fmt.Errorf("constant index not found: %d", curIdx)) - } - copy(insts[i:], MakeInstruction(op, newIdx)) - case OpClosure: - curIdx := int(insts[i+2]) | int(insts[i+1])<<8 - numFree := int(insts[i+3]) - newIdx, ok := indexMap[curIdx] - if !ok { - panic(fmt.Errorf("constant index not found: %d", curIdx)) - } - copy(insts[i:], MakeInstruction(op, newIdx, numFree)) - } - - i += 1 + read - } -} - -func moduleName(mod *objects.ImmutableMap) string { - if modName, ok := mod.Value["__module_name__"].(*objects.String); ok { - return modName.Value - } - - return "" -} diff --git a/compiler/bytecode_test.go b/compiler/bytecode_test.go deleted file mode 100644 index ad6ce0b..0000000 --- a/compiler/bytecode_test.go +++ /dev/null @@ -1,289 +0,0 @@ -package compiler_test - -import ( - "bytes" - "testing" - "time" - - "github.com/d5/tengo/assert" - "github.com/d5/tengo/compiler" - "github.com/d5/tengo/compiler/source" - "github.com/d5/tengo/objects" -) - -type srcfile struct { - name string - size int -} - -func TestBytecode(t *testing.T) { - testBytecodeSerialization(t, bytecode(concat(), objectsArray())) - - testBytecodeSerialization(t, bytecode( - concat(), objectsArray( - &objects.Char{Value: 'y'}, - &objects.Float{Value: 93.11}, - compiledFunction(1, 0, - compiler.MakeInstruction(compiler.OpConstant, 3), - compiler.MakeInstruction(compiler.OpSetLocal, 0), - compiler.MakeInstruction(compiler.OpGetGlobal, 0), - compiler.MakeInstruction(compiler.OpGetFree, 0)), - &objects.Float{Value: 39.2}, - &objects.Int{Value: 192}, - &objects.String{Value: "bar"}))) - - testBytecodeSerialization(t, bytecodeFileSet( - concat( - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpSetGlobal, 0), - compiler.MakeInstruction(compiler.OpConstant, 6), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - &objects.Int{Value: 55}, - &objects.Int{Value: 66}, - &objects.Int{Value: 77}, - &objects.Int{Value: 88}, - &objects.ImmutableMap{ - Value: map[string]objects.Object{ - "array": &objects.ImmutableArray{ - Value: []objects.Object{ - &objects.Int{Value: 1}, - &objects.Int{Value: 2}, - &objects.Int{Value: 3}, - objects.TrueValue, - objects.FalseValue, - objects.UndefinedValue, - }, - }, - "true": objects.TrueValue, - "false": objects.FalseValue, - "bytes": &objects.Bytes{Value: make([]byte, 16)}, - "char": &objects.Char{Value: 'Y'}, - "error": &objects.Error{Value: &objects.String{Value: "some error"}}, - "float": &objects.Float{Value: -19.84}, - "immutable_array": &objects.ImmutableArray{ - Value: []objects.Object{ - &objects.Int{Value: 1}, - &objects.Int{Value: 2}, - &objects.Int{Value: 3}, - objects.TrueValue, - objects.FalseValue, - objects.UndefinedValue, - }, - }, - "immutable_map": &objects.ImmutableMap{ - Value: map[string]objects.Object{ - "a": &objects.Int{Value: 1}, - "b": &objects.Int{Value: 2}, - "c": &objects.Int{Value: 3}, - "d": objects.TrueValue, - "e": objects.FalseValue, - "f": objects.UndefinedValue, - }, - }, - "int": &objects.Int{Value: 91}, - "map": &objects.Map{ - Value: map[string]objects.Object{ - "a": &objects.Int{Value: 1}, - "b": &objects.Int{Value: 2}, - "c": &objects.Int{Value: 3}, - "d": objects.TrueValue, - "e": objects.FalseValue, - "f": objects.UndefinedValue, - }, - }, - "string": &objects.String{Value: "foo bar"}, - "time": &objects.Time{Value: time.Now()}, - "undefined": objects.UndefinedValue, - }, - }, - compiledFunction(1, 0, - compiler.MakeInstruction(compiler.OpConstant, 3), - compiler.MakeInstruction(compiler.OpSetLocal, 0), - compiler.MakeInstruction(compiler.OpGetGlobal, 0), - compiler.MakeInstruction(compiler.OpGetFree, 0), - compiler.MakeInstruction(compiler.OpBinaryOp, 11), - compiler.MakeInstruction(compiler.OpGetFree, 1), - compiler.MakeInstruction(compiler.OpBinaryOp, 11), - compiler.MakeInstruction(compiler.OpGetLocal, 0), - compiler.MakeInstruction(compiler.OpBinaryOp, 11), - compiler.MakeInstruction(compiler.OpReturn, 1)), - compiledFunction(1, 0, - compiler.MakeInstruction(compiler.OpConstant, 2), - compiler.MakeInstruction(compiler.OpSetLocal, 0), - compiler.MakeInstruction(compiler.OpGetFree, 0), - compiler.MakeInstruction(compiler.OpGetLocal, 0), - compiler.MakeInstruction(compiler.OpClosure, 4, 2), - compiler.MakeInstruction(compiler.OpReturn, 1)), - compiledFunction(1, 0, - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpSetLocal, 0), - compiler.MakeInstruction(compiler.OpGetLocal, 0), - compiler.MakeInstruction(compiler.OpClosure, 5, 1), - compiler.MakeInstruction(compiler.OpReturn, 1))), - fileSet(srcfile{name: "file1", size: 100}, srcfile{name: "file2", size: 200}))) -} - -func TestBytecode_RemoveDuplicates(t *testing.T) { - testBytecodeRemoveDuplicates(t, - bytecode( - concat(), objectsArray( - &objects.Char{Value: 'y'}, - &objects.Float{Value: 93.11}, - compiledFunction(1, 0, - compiler.MakeInstruction(compiler.OpConstant, 3), - compiler.MakeInstruction(compiler.OpSetLocal, 0), - compiler.MakeInstruction(compiler.OpGetGlobal, 0), - compiler.MakeInstruction(compiler.OpGetFree, 0)), - &objects.Float{Value: 39.2}, - &objects.Int{Value: 192}, - &objects.String{Value: "bar"})), - bytecode( - concat(), objectsArray( - &objects.Char{Value: 'y'}, - &objects.Float{Value: 93.11}, - compiledFunction(1, 0, - compiler.MakeInstruction(compiler.OpConstant, 3), - compiler.MakeInstruction(compiler.OpSetLocal, 0), - compiler.MakeInstruction(compiler.OpGetGlobal, 0), - compiler.MakeInstruction(compiler.OpGetFree, 0)), - &objects.Float{Value: 39.2}, - &objects.Int{Value: 192}, - &objects.String{Value: "bar"}))) - - testBytecodeRemoveDuplicates(t, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpConstant, 2), - compiler.MakeInstruction(compiler.OpConstant, 3), - compiler.MakeInstruction(compiler.OpConstant, 4), - compiler.MakeInstruction(compiler.OpConstant, 5), - compiler.MakeInstruction(compiler.OpConstant, 6), - compiler.MakeInstruction(compiler.OpConstant, 7), - compiler.MakeInstruction(compiler.OpConstant, 8), - compiler.MakeInstruction(compiler.OpClosure, 4, 1)), - objectsArray( - &objects.Int{Value: 1}, - &objects.Float{Value: 2.0}, - &objects.Char{Value: '3'}, - &objects.String{Value: "four"}, - compiledFunction(1, 0, - compiler.MakeInstruction(compiler.OpConstant, 3), - compiler.MakeInstruction(compiler.OpConstant, 7), - compiler.MakeInstruction(compiler.OpSetLocal, 0), - compiler.MakeInstruction(compiler.OpGetGlobal, 0), - compiler.MakeInstruction(compiler.OpGetFree, 0)), - &objects.Int{Value: 1}, - &objects.Float{Value: 2.0}, - &objects.Char{Value: '3'}, - &objects.String{Value: "four"})), - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpConstant, 2), - compiler.MakeInstruction(compiler.OpConstant, 3), - compiler.MakeInstruction(compiler.OpConstant, 4), - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpConstant, 2), - compiler.MakeInstruction(compiler.OpConstant, 3), - compiler.MakeInstruction(compiler.OpClosure, 4, 1)), - objectsArray( - &objects.Int{Value: 1}, - &objects.Float{Value: 2.0}, - &objects.Char{Value: '3'}, - &objects.String{Value: "four"}, - compiledFunction(1, 0, - compiler.MakeInstruction(compiler.OpConstant, 3), - compiler.MakeInstruction(compiler.OpConstant, 2), - compiler.MakeInstruction(compiler.OpSetLocal, 0), - compiler.MakeInstruction(compiler.OpGetGlobal, 0), - compiler.MakeInstruction(compiler.OpGetFree, 0))))) - - testBytecodeRemoveDuplicates(t, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpConstant, 2), - compiler.MakeInstruction(compiler.OpConstant, 3), - compiler.MakeInstruction(compiler.OpConstant, 4)), - objectsArray( - &objects.Int{Value: 1}, - &objects.Int{Value: 2}, - &objects.Int{Value: 3}, - &objects.Int{Value: 1}, - &objects.Int{Value: 3})), - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpConstant, 2), - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpConstant, 2)), - objectsArray( - &objects.Int{Value: 1}, - &objects.Int{Value: 2}, - &objects.Int{Value: 3}))) -} - -func TestBytecode_CountObjects(t *testing.T) { - b := bytecode( - concat(), - objectsArray( - &objects.Int{Value: 55}, - &objects.Int{Value: 66}, - &objects.Int{Value: 77}, - &objects.Int{Value: 88}, - compiledFunction(1, 0, - compiler.MakeInstruction(compiler.OpConstant, 3), - compiler.MakeInstruction(compiler.OpReturn, 1)), - compiledFunction(1, 0, - compiler.MakeInstruction(compiler.OpConstant, 2), - compiler.MakeInstruction(compiler.OpReturn, 1)), - compiledFunction(1, 0, - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpReturn, 1)))) - assert.Equal(t, 7, b.CountObjects()) -} - -func fileSet(files ...srcfile) *source.FileSet { - fileSet := source.NewFileSet() - for _, f := range files { - fileSet.AddFile(f.name, -1, f.size) - } - return fileSet -} - -func bytecodeFileSet(instructions []byte, constants []objects.Object, fileSet *source.FileSet) *compiler.Bytecode { - return &compiler.Bytecode{ - FileSet: fileSet, - MainFunction: &objects.CompiledFunction{Instructions: instructions}, - Constants: constants, - } -} - -func testBytecodeRemoveDuplicates(t *testing.T, input, expected *compiler.Bytecode) { - input.RemoveDuplicates() - - assert.Equal(t, expected.FileSet, input.FileSet) - assert.Equal(t, expected.MainFunction, input.MainFunction) - assert.Equal(t, expected.Constants, input.Constants) -} - -func testBytecodeSerialization(t *testing.T, b *compiler.Bytecode) { - var buf bytes.Buffer - err := b.Encode(&buf) - assert.NoError(t, err) - - r := &compiler.Bytecode{} - err = r.Decode(bytes.NewReader(buf.Bytes()), nil) - assert.NoError(t, err) - - assert.Equal(t, b.FileSet, r.FileSet) - assert.Equal(t, b.MainFunction, r.MainFunction) - assert.Equal(t, b.Constants, r.Constants) -} diff --git a/compiler/compilation_scope.go b/compiler/compilation_scope.go deleted file mode 100644 index 41e7876..0000000 --- a/compiler/compilation_scope.go +++ /dev/null @@ -1,11 +0,0 @@ -package compiler - -import "github.com/d5/tengo/compiler/source" - -// CompilationScope represents a compiled instructions -// and the last two instructions that were emitted. -type CompilationScope struct { - instructions []byte - symbolInit map[string]bool - sourceMap map[int]source.Pos -} diff --git a/compiler/compiler.go b/compiler/compiler.go deleted file mode 100644 index 8bde5dc..0000000 --- a/compiler/compiler.go +++ /dev/null @@ -1,846 +0,0 @@ -package compiler - -import ( - "fmt" - "io" - "io/ioutil" - "path/filepath" - "reflect" - "strings" - - "github.com/d5/tengo" - "github.com/d5/tengo/compiler/ast" - "github.com/d5/tengo/compiler/source" - "github.com/d5/tengo/compiler/token" - "github.com/d5/tengo/objects" -) - -// Compiler compiles the AST into a bytecode. -type Compiler struct { - file *source.File - parent *Compiler - modulePath string - constants []objects.Object - symbolTable *SymbolTable - scopes []CompilationScope - scopeIndex int - modules *objects.ModuleMap - compiledModules map[string]*objects.CompiledFunction - allowFileImport bool - loops []*Loop - loopIndex int - trace io.Writer - indent int -} - -// NewCompiler creates a Compiler. -func NewCompiler(file *source.File, symbolTable *SymbolTable, constants []objects.Object, modules *objects.ModuleMap, trace io.Writer) *Compiler { - mainScope := CompilationScope{ - symbolInit: make(map[string]bool), - sourceMap: make(map[int]source.Pos), - } - - // symbol table - if symbolTable == nil { - symbolTable = NewSymbolTable() - } - - // add builtin functions to the symbol table - for idx, fn := range objects.Builtins { - symbolTable.DefineBuiltin(idx, fn.Name) - } - - // builtin modules - if modules == nil { - modules = objects.NewModuleMap() - } - - return &Compiler{ - file: file, - symbolTable: symbolTable, - constants: constants, - scopes: []CompilationScope{mainScope}, - scopeIndex: 0, - loopIndex: -1, - trace: trace, - modules: modules, - compiledModules: make(map[string]*objects.CompiledFunction), - } -} - -// Compile compiles the AST node. -func (c *Compiler) Compile(node ast.Node) error { - if c.trace != nil { - if node != nil { - defer un(trace(c, fmt.Sprintf("%s (%s)", node.String(), reflect.TypeOf(node).Elem().Name()))) - } else { - defer un(trace(c, "")) - } - } - - switch node := node.(type) { - case *ast.File: - for _, stmt := range node.Stmts { - if err := c.Compile(stmt); err != nil { - return err - } - } - - case *ast.ExprStmt: - if err := c.Compile(node.Expr); err != nil { - return err - } - c.emit(node, OpPop) - - case *ast.IncDecStmt: - op := token.AddAssign - if node.Token == token.Dec { - op = token.SubAssign - } - - return c.compileAssign(node, []ast.Expr{node.Expr}, []ast.Expr{&ast.IntLit{Value: 1}}, op) - - case *ast.ParenExpr: - if err := c.Compile(node.Expr); err != nil { - return err - } - - case *ast.BinaryExpr: - if node.Token == token.LAnd || node.Token == token.LOr { - return c.compileLogical(node) - } - - if node.Token == token.Less { - if err := c.Compile(node.RHS); err != nil { - return err - } - - if err := c.Compile(node.LHS); err != nil { - return err - } - - c.emit(node, OpBinaryOp, int(token.Greater)) - - return nil - } else if node.Token == token.LessEq { - if err := c.Compile(node.RHS); err != nil { - return err - } - if err := c.Compile(node.LHS); err != nil { - return err - } - - c.emit(node, OpBinaryOp, int(token.GreaterEq)) - - return nil - } - - if err := c.Compile(node.LHS); err != nil { - return err - } - if err := c.Compile(node.RHS); err != nil { - return err - } - - switch node.Token { - case token.Add: - c.emit(node, OpBinaryOp, int(token.Add)) - case token.Sub: - c.emit(node, OpBinaryOp, int(token.Sub)) - case token.Mul: - c.emit(node, OpBinaryOp, int(token.Mul)) - case token.Quo: - c.emit(node, OpBinaryOp, int(token.Quo)) - case token.Rem: - c.emit(node, OpBinaryOp, int(token.Rem)) - case token.Greater: - c.emit(node, OpBinaryOp, int(token.Greater)) - case token.GreaterEq: - c.emit(node, OpBinaryOp, int(token.GreaterEq)) - case token.Equal: - c.emit(node, OpEqual) - case token.NotEqual: - c.emit(node, OpNotEqual) - case token.And: - c.emit(node, OpBinaryOp, int(token.And)) - case token.Or: - c.emit(node, OpBinaryOp, int(token.Or)) - case token.Xor: - c.emit(node, OpBinaryOp, int(token.Xor)) - case token.AndNot: - c.emit(node, OpBinaryOp, int(token.AndNot)) - case token.Shl: - c.emit(node, OpBinaryOp, int(token.Shl)) - case token.Shr: - c.emit(node, OpBinaryOp, int(token.Shr)) - default: - return c.errorf(node, "invalid binary operator: %s", node.Token.String()) - } - - case *ast.IntLit: - c.emit(node, OpConstant, c.addConstant(&objects.Int{Value: node.Value})) - - case *ast.FloatLit: - c.emit(node, OpConstant, c.addConstant(&objects.Float{Value: node.Value})) - - case *ast.BoolLit: - if node.Value { - c.emit(node, OpTrue) - } else { - c.emit(node, OpFalse) - } - - case *ast.StringLit: - if len(node.Value) > tengo.MaxStringLen { - return c.error(node, objects.ErrStringLimit) - } - - c.emit(node, OpConstant, c.addConstant(&objects.String{Value: node.Value})) - - case *ast.CharLit: - c.emit(node, OpConstant, c.addConstant(&objects.Char{Value: node.Value})) - - case *ast.UndefinedLit: - c.emit(node, OpNull) - - case *ast.UnaryExpr: - if err := c.Compile(node.Expr); err != nil { - return err - } - - switch node.Token { - case token.Not: - c.emit(node, OpLNot) - case token.Sub: - c.emit(node, OpMinus) - case token.Xor: - c.emit(node, OpBComplement) - case token.Add: - // do nothing? - default: - return c.errorf(node, "invalid unary operator: %s", node.Token.String()) - } - - case *ast.IfStmt: - // open new symbol table for the statement - c.symbolTable = c.symbolTable.Fork(true) - defer func() { - c.symbolTable = c.symbolTable.Parent(false) - }() - - if node.Init != nil { - if err := c.Compile(node.Init); err != nil { - return err - } - } - - if err := c.Compile(node.Cond); err != nil { - return err - } - - // first jump placeholder - jumpPos1 := c.emit(node, OpJumpFalsy, 0) - - if err := c.Compile(node.Body); err != nil { - return err - } - - if node.Else != nil { - // second jump placeholder - jumpPos2 := c.emit(node, OpJump, 0) - - // update first jump offset - curPos := len(c.currentInstructions()) - c.changeOperand(jumpPos1, curPos) - - if err := c.Compile(node.Else); err != nil { - return err - } - - // update second jump offset - curPos = len(c.currentInstructions()) - c.changeOperand(jumpPos2, curPos) - } else { - // update first jump offset - curPos := len(c.currentInstructions()) - c.changeOperand(jumpPos1, curPos) - } - - case *ast.ForStmt: - return c.compileForStmt(node) - - case *ast.ForInStmt: - return c.compileForInStmt(node) - - case *ast.BranchStmt: - if node.Token == token.Break { - curLoop := c.currentLoop() - if curLoop == nil { - return c.errorf(node, "break not allowed outside loop") - } - pos := c.emit(node, OpJump, 0) - curLoop.Breaks = append(curLoop.Breaks, pos) - } else if node.Token == token.Continue { - curLoop := c.currentLoop() - if curLoop == nil { - return c.errorf(node, "continue not allowed outside loop") - } - pos := c.emit(node, OpJump, 0) - curLoop.Continues = append(curLoop.Continues, pos) - } else { - panic(fmt.Errorf("invalid branch statement: %s", node.Token.String())) - } - - case *ast.BlockStmt: - if len(node.Stmts) == 0 { - return nil - } - - c.symbolTable = c.symbolTable.Fork(true) - defer func() { - c.symbolTable = c.symbolTable.Parent(false) - }() - - for _, stmt := range node.Stmts { - if err := c.Compile(stmt); err != nil { - return err - } - } - - case *ast.AssignStmt: - if err := c.compileAssign(node, node.LHS, node.RHS, node.Token); err != nil { - return err - } - - case *ast.Ident: - symbol, _, ok := c.symbolTable.Resolve(node.Name) - if !ok { - return c.errorf(node, "unresolved reference '%s'", node.Name) - } - - switch symbol.Scope { - case ScopeGlobal: - c.emit(node, OpGetGlobal, symbol.Index) - case ScopeLocal: - c.emit(node, OpGetLocal, symbol.Index) - case ScopeBuiltin: - c.emit(node, OpGetBuiltin, symbol.Index) - case ScopeFree: - c.emit(node, OpGetFree, symbol.Index) - } - - case *ast.ArrayLit: - for _, elem := range node.Elements { - if err := c.Compile(elem); err != nil { - return err - } - } - - c.emit(node, OpArray, len(node.Elements)) - - case *ast.MapLit: - for _, elt := range node.Elements { - // key - if len(elt.Key) > tengo.MaxStringLen { - return c.error(node, objects.ErrStringLimit) - } - c.emit(node, OpConstant, c.addConstant(&objects.String{Value: elt.Key})) - - // value - if err := c.Compile(elt.Value); err != nil { - return err - } - } - - c.emit(node, OpMap, len(node.Elements)*2) - - case *ast.SelectorExpr: // selector on RHS side - if err := c.Compile(node.Expr); err != nil { - return err - } - - if err := c.Compile(node.Sel); err != nil { - return err - } - - c.emit(node, OpIndex) - - case *ast.IndexExpr: - if err := c.Compile(node.Expr); err != nil { - return err - } - - if err := c.Compile(node.Index); err != nil { - return err - } - - c.emit(node, OpIndex) - - case *ast.SliceExpr: - if err := c.Compile(node.Expr); err != nil { - return err - } - - if node.Low != nil { - if err := c.Compile(node.Low); err != nil { - return err - } - } else { - c.emit(node, OpNull) - } - - if node.High != nil { - if err := c.Compile(node.High); err != nil { - return err - } - } else { - c.emit(node, OpNull) - } - - c.emit(node, OpSliceIndex) - - case *ast.FuncLit: - c.enterScope() - - for _, p := range node.Type.Params.List { - s := c.symbolTable.Define(p.Name) - - // function arguments is not assigned directly. - s.LocalAssigned = true - } - - if err := c.Compile(node.Body); err != nil { - return err - } - - // code optimization - c.optimizeFunc(node) - - freeSymbols := c.symbolTable.FreeSymbols() - numLocals := c.symbolTable.MaxSymbols() - instructions, sourceMap := c.leaveScope() - - for _, s := range freeSymbols { - switch s.Scope { - case ScopeLocal: - if !s.LocalAssigned { - // Here, the closure is capturing a local variable that's not yet assigned its value. - // One example is a local recursive function: - // - // func() { - // foo := func(x) { - // // .. - // return foo(x-1) - // } - // } - // - // which translate into - // - // 0000 GETL 0 - // 0002 CLOSURE ? 1 - // 0006 DEFL 0 - // - // . So the local variable (0) is being captured before it's assigned the value. - // - // Solution is to transform the code into something like this: - // - // func() { - // foo := undefined - // foo = func(x) { - // // .. - // return foo(x-1) - // } - // } - // - // that is equivalent to - // - // 0000 NULL - // 0001 DEFL 0 - // 0003 GETL 0 - // 0005 CLOSURE ? 1 - // 0009 SETL 0 - // - - c.emit(node, OpNull) - c.emit(node, OpDefineLocal, s.Index) - - s.LocalAssigned = true - } - - c.emit(node, OpGetLocalPtr, s.Index) - case ScopeFree: - c.emit(node, OpGetFreePtr, s.Index) - } - } - - compiledFunction := &objects.CompiledFunction{ - Instructions: instructions, - NumLocals: numLocals, - NumParameters: len(node.Type.Params.List), - VarArgs: node.Type.Params.VarArgs, - SourceMap: sourceMap, - } - - if len(freeSymbols) > 0 { - c.emit(node, OpClosure, c.addConstant(compiledFunction), len(freeSymbols)) - } else { - c.emit(node, OpConstant, c.addConstant(compiledFunction)) - } - - case *ast.ReturnStmt: - if c.symbolTable.Parent(true) == nil { - // outside the function - return c.errorf(node, "return not allowed outside function") - } - - if node.Result == nil { - c.emit(node, OpReturn, 0) - } else { - if err := c.Compile(node.Result); err != nil { - return err - } - - c.emit(node, OpReturn, 1) - } - - case *ast.CallExpr: - if err := c.Compile(node.Func); err != nil { - return err - } - - for _, arg := range node.Args { - if err := c.Compile(arg); err != nil { - return err - } - } - - c.emit(node, OpCall, len(node.Args)) - - case *ast.ImportExpr: - if node.ModuleName == "" { - return c.errorf(node, "empty module name") - } - - if mod := c.modules.Get(node.ModuleName); mod != nil { - v, err := mod.Import(node.ModuleName) - if err != nil { - return err - } - - switch v := v.(type) { - case []byte: // module written in Tengo - compiled, err := c.compileModule(node, node.ModuleName, node.ModuleName, v) - if err != nil { - return err - } - c.emit(node, OpConstant, c.addConstant(compiled)) - c.emit(node, OpCall, 0) - case objects.Object: // builtin module - c.emit(node, OpConstant, c.addConstant(v)) - default: - panic(fmt.Errorf("invalid import value type: %T", v)) - } - } else if c.allowFileImport { - moduleName := node.ModuleName - if !strings.HasSuffix(moduleName, ".tengo") { - moduleName += ".tengo" - } - - modulePath, err := filepath.Abs(moduleName) - if err != nil { - return c.errorf(node, "module file path error: %s", err.Error()) - } - - if err := c.checkCyclicImports(node, modulePath); err != nil { - return err - } - - moduleSrc, err := ioutil.ReadFile(moduleName) - if err != nil { - return c.errorf(node, "module file read error: %s", err.Error()) - } - - compiled, err := c.compileModule(node, moduleName, modulePath, moduleSrc) - if err != nil { - return err - } - c.emit(node, OpConstant, c.addConstant(compiled)) - c.emit(node, OpCall, 0) - } else { - return c.errorf(node, "module '%s' not found", node.ModuleName) - } - - case *ast.ExportStmt: - // export statement must be in top-level scope - if c.scopeIndex != 0 { - return c.errorf(node, "export not allowed inside function") - } - - // export statement is simply ignore when compiling non-module code - if c.parent == nil { - break - } - - if err := c.Compile(node.Result); err != nil { - return err - } - - c.emit(node, OpImmutable) - c.emit(node, OpReturn, 1) - - case *ast.ErrorExpr: - if err := c.Compile(node.Expr); err != nil { - return err - } - - c.emit(node, OpError) - - case *ast.ImmutableExpr: - if err := c.Compile(node.Expr); err != nil { - return err - } - - c.emit(node, OpImmutable) - - case *ast.CondExpr: - if err := c.Compile(node.Cond); err != nil { - return err - } - - // first jump placeholder - jumpPos1 := c.emit(node, OpJumpFalsy, 0) - - if err := c.Compile(node.True); err != nil { - return err - } - - // second jump placeholder - jumpPos2 := c.emit(node, OpJump, 0) - - // update first jump offset - curPos := len(c.currentInstructions()) - c.changeOperand(jumpPos1, curPos) - - if err := c.Compile(node.False); err != nil { - return err - } - - // update second jump offset - curPos = len(c.currentInstructions()) - c.changeOperand(jumpPos2, curPos) - } - - return nil -} - -// Bytecode returns a compiled bytecode. -func (c *Compiler) Bytecode() *Bytecode { - return &Bytecode{ - FileSet: c.file.Set(), - MainFunction: &objects.CompiledFunction{ - Instructions: c.currentInstructions(), - SourceMap: c.currentSourceMap(), - }, - Constants: c.constants, - } -} - -// EnableFileImport enables or disables module loading from local files. -// Local file modules are disabled by default. -func (c *Compiler) EnableFileImport(enable bool) { - c.allowFileImport = enable -} - -func (c *Compiler) fork(file *source.File, modulePath string, symbolTable *SymbolTable) *Compiler { - child := NewCompiler(file, symbolTable, nil, c.modules, c.trace) - child.modulePath = modulePath // module file path - child.parent = c // parent to set to current compiler - - return child -} - -func (c *Compiler) error(node ast.Node, err error) error { - return &Error{ - fileSet: c.file.Set(), - node: node, - error: err, - } -} - -func (c *Compiler) errorf(node ast.Node, format string, args ...interface{}) error { - return &Error{ - fileSet: c.file.Set(), - node: node, - error: fmt.Errorf(format, args...), - } -} - -func (c *Compiler) addConstant(o objects.Object) int { - if c.parent != nil { - // module compilers will use their parent's constants array - return c.parent.addConstant(o) - } - - c.constants = append(c.constants, o) - - if c.trace != nil { - c.printTrace(fmt.Sprintf("CONST %04d %s", len(c.constants)-1, o)) - } - - return len(c.constants) - 1 -} - -func (c *Compiler) addInstruction(b []byte) int { - posNewIns := len(c.currentInstructions()) - - c.scopes[c.scopeIndex].instructions = append(c.currentInstructions(), b...) - - return posNewIns -} - -func (c *Compiler) replaceInstruction(pos int, inst []byte) { - copy(c.currentInstructions()[pos:], inst) - - if c.trace != nil { - c.printTrace(fmt.Sprintf("REPLC %s", - FormatInstructions(c.scopes[c.scopeIndex].instructions[pos:], pos)[0])) - } -} - -func (c *Compiler) changeOperand(opPos int, operand ...int) { - op := Opcode(c.currentInstructions()[opPos]) - inst := MakeInstruction(op, operand...) - - c.replaceInstruction(opPos, inst) -} - -// optimizeFunc performs some code-level optimization for the current function instructions -// it removes unreachable (dead code) instructions and adds "returns" instruction if needed. -func (c *Compiler) optimizeFunc(node ast.Node) { - // any instructions between RETURN and the function end - // or instructions between RETURN and jump target position - // are considered as unreachable. - - // pass 1. identify all jump destinations - dsts := make(map[int]bool) - iterateInstructions(c.scopes[c.scopeIndex].instructions, func(pos int, opcode Opcode, operands []int) bool { - switch opcode { - case OpJump, OpJumpFalsy, OpAndJump, OpOrJump: - dsts[operands[0]] = true - } - - return true - }) - - var newInsts []byte - - // pass 2. eliminate dead code - posMap := make(map[int]int) // old position to new position - var dstIdx int - var deadCode bool - iterateInstructions(c.scopes[c.scopeIndex].instructions, func(pos int, opcode Opcode, operands []int) bool { - switch { - case opcode == OpReturn: - if deadCode { - return true - } - deadCode = true - case dsts[pos]: - dstIdx++ - deadCode = false - case deadCode: - return true - } - - posMap[pos] = len(newInsts) - newInsts = append(newInsts, MakeInstruction(opcode, operands...)...) - return true - }) - - // pass 3. update jump positions - var lastOp Opcode - var appendReturn bool - endPos := len(c.scopes[c.scopeIndex].instructions) - iterateInstructions(newInsts, func(pos int, opcode Opcode, operands []int) bool { - switch opcode { - case OpJump, OpJumpFalsy, OpAndJump, OpOrJump: - newDst, ok := posMap[operands[0]] - if ok { - copy(newInsts[pos:], MakeInstruction(opcode, newDst)) - } else if endPos == operands[0] { - // there's a jump instruction that jumps to the end of function - // compiler should append "return". - appendReturn = true - } else { - panic(fmt.Errorf("invalid jump position: %d", newDst)) - } - } - lastOp = opcode - return true - }) - if lastOp != OpReturn { - appendReturn = true - } - - // pass 4. update source map - newSourceMap := make(map[int]source.Pos) - for pos, srcPos := range c.scopes[c.scopeIndex].sourceMap { - newPos, ok := posMap[pos] - if ok { - newSourceMap[newPos] = srcPos - } - } - - c.scopes[c.scopeIndex].instructions = newInsts - c.scopes[c.scopeIndex].sourceMap = newSourceMap - - // append "return" - if appendReturn { - c.emit(node, OpReturn, 0) - } -} - -func (c *Compiler) emit(node ast.Node, opcode Opcode, operands ...int) int { - filePos := source.NoPos - if node != nil { - filePos = node.Pos() - } - - inst := MakeInstruction(opcode, operands...) - pos := c.addInstruction(inst) - c.scopes[c.scopeIndex].sourceMap[pos] = filePos - - if c.trace != nil { - c.printTrace(fmt.Sprintf("EMIT %s", - FormatInstructions(c.scopes[c.scopeIndex].instructions[pos:], pos)[0])) - } - - return pos -} - -func (c *Compiler) printTrace(a ...interface{}) { - const ( - dots = ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . " - n = len(dots) - ) - - i := 2 * c.indent - for i > n { - _, _ = fmt.Fprint(c.trace, dots) - i -= n - } - _, _ = fmt.Fprint(c.trace, dots[0:i]) - _, _ = fmt.Fprintln(c.trace, a...) -} - -func trace(c *Compiler, msg string) *Compiler { - c.printTrace(msg, "{") - c.indent++ - - return c -} - -func un(c *Compiler) { - c.indent-- - c.printTrace("}") -} diff --git a/compiler/compiler_assign.go b/compiler/compiler_assign.go deleted file mode 100644 index 59296f6..0000000 --- a/compiler/compiler_assign.go +++ /dev/null @@ -1,133 +0,0 @@ -package compiler - -import ( - "fmt" - - "github.com/d5/tengo/compiler/ast" - "github.com/d5/tengo/compiler/token" -) - -func (c *Compiler) compileAssign(node ast.Node, lhs, rhs []ast.Expr, op token.Token) error { - numLHS, numRHS := len(lhs), len(rhs) - if numLHS > 1 || numRHS > 1 { - return c.errorf(node, "tuple assignment not allowed") - } - - // resolve and compile left-hand side - ident, selectors := resolveAssignLHS(lhs[0]) - numSel := len(selectors) - - if op == token.Define && numSel > 0 { - // using selector on new variable does not make sense - return c.errorf(node, "operator ':=' not allowed with selector") - } - - symbol, depth, exists := c.symbolTable.Resolve(ident) - if op == token.Define { - if depth == 0 && exists { - return c.errorf(node, "'%s' redeclared in this block", ident) - } - - symbol = c.symbolTable.Define(ident) - } else { - if !exists { - return c.errorf(node, "unresolved reference '%s'", ident) - } - } - - // +=, -=, *=, /= - if op != token.Assign && op != token.Define { - if err := c.Compile(lhs[0]); err != nil { - return err - } - } - - // compile RHSs - for _, expr := range rhs { - if err := c.Compile(expr); err != nil { - return err - } - } - - switch op { - case token.AddAssign: - c.emit(node, OpBinaryOp, int(token.Add)) - case token.SubAssign: - c.emit(node, OpBinaryOp, int(token.Sub)) - case token.MulAssign: - c.emit(node, OpBinaryOp, int(token.Mul)) - case token.QuoAssign: - c.emit(node, OpBinaryOp, int(token.Quo)) - case token.RemAssign: - c.emit(node, OpBinaryOp, int(token.Rem)) - case token.AndAssign: - c.emit(node, OpBinaryOp, int(token.And)) - case token.OrAssign: - c.emit(node, OpBinaryOp, int(token.Or)) - case token.AndNotAssign: - c.emit(node, OpBinaryOp, int(token.AndNot)) - case token.XorAssign: - c.emit(node, OpBinaryOp, int(token.Xor)) - case token.ShlAssign: - c.emit(node, OpBinaryOp, int(token.Shl)) - case token.ShrAssign: - c.emit(node, OpBinaryOp, int(token.Shr)) - } - - // compile selector expressions (right to left) - for i := numSel - 1; i >= 0; i-- { - if err := c.Compile(selectors[i]); err != nil { - return err - } - } - - switch symbol.Scope { - case ScopeGlobal: - if numSel > 0 { - c.emit(node, OpSetSelGlobal, symbol.Index, numSel) - } else { - c.emit(node, OpSetGlobal, symbol.Index) - } - case ScopeLocal: - if numSel > 0 { - c.emit(node, OpSetSelLocal, symbol.Index, numSel) - } else { - if op == token.Define && !symbol.LocalAssigned { - c.emit(node, OpDefineLocal, symbol.Index) - } else { - c.emit(node, OpSetLocal, symbol.Index) - } - } - - // mark the symbol as local-assigned - symbol.LocalAssigned = true - case ScopeFree: - if numSel > 0 { - c.emit(node, OpSetSelFree, symbol.Index, numSel) - } else { - c.emit(node, OpSetFree, symbol.Index) - } - default: - panic(fmt.Errorf("invalid assignment variable scope: %s", symbol.Scope)) - } - - return nil -} - -func resolveAssignLHS(expr ast.Expr) (name string, selectors []ast.Expr) { - switch term := expr.(type) { - case *ast.SelectorExpr: - name, selectors = resolveAssignLHS(term.Expr) - selectors = append(selectors, term.Sel) - return - - case *ast.IndexExpr: - name, selectors = resolveAssignLHS(term.Expr) - selectors = append(selectors, term.Index) - - case *ast.Ident: - name = term.Name - } - - return -} diff --git a/compiler/compiler_error_report_test.go b/compiler/compiler_error_report_test.go deleted file mode 100644 index c357c81..0000000 --- a/compiler/compiler_error_report_test.go +++ /dev/null @@ -1,17 +0,0 @@ -package compiler_test - -import "testing" - -func TestCompilerErrorReport(t *testing.T) { - expectError(t, `import("user1")`, "Compile Error: module 'user1' not found\n\tat test:1:1") - - expectError(t, `a = 1`, "Compile Error: unresolved reference 'a'\n\tat test:1:1") - expectError(t, `a, b := 1, 2`, "Compile Error: tuple assignment not allowed\n\tat test:1:1") - expectError(t, `a.b := 1`, "not allowed with selector") - expectError(t, `a:=1; a:=3`, "Compile Error: 'a' redeclared in this block\n\tat test:1:7") - - expectError(t, `return 5`, "Compile Error: return not allowed outside function\n\tat test:1:1") - expectError(t, `func() { break }`, "Compile Error: break not allowed outside loop\n\tat test:1:10") - expectError(t, `func() { continue }`, "Compile Error: continue not allowed outside loop\n\tat test:1:10") - expectError(t, `func() { export 5 }`, "Compile Error: export not allowed inside function\n\tat test:1:10") -} diff --git a/compiler/compiler_for.go b/compiler/compiler_for.go deleted file mode 100644 index e7b7b5f..0000000 --- a/compiler/compiler_for.go +++ /dev/null @@ -1,181 +0,0 @@ -package compiler - -import ( - "github.com/d5/tengo/compiler/ast" -) - -func (c *Compiler) compileForStmt(stmt *ast.ForStmt) error { - c.symbolTable = c.symbolTable.Fork(true) - defer func() { - c.symbolTable = c.symbolTable.Parent(false) - }() - - // init statement - if stmt.Init != nil { - if err := c.Compile(stmt.Init); err != nil { - return err - } - } - - // pre-condition position - preCondPos := len(c.currentInstructions()) - - // condition expression - postCondPos := -1 - if stmt.Cond != nil { - if err := c.Compile(stmt.Cond); err != nil { - return err - } - // condition jump position - postCondPos = c.emit(stmt, OpJumpFalsy, 0) - } - - // enter loop - loop := c.enterLoop() - - // body statement - if err := c.Compile(stmt.Body); err != nil { - c.leaveLoop() - return err - } - - c.leaveLoop() - - // post-body position - postBodyPos := len(c.currentInstructions()) - - // post statement - if stmt.Post != nil { - if err := c.Compile(stmt.Post); err != nil { - return err - } - } - - // back to condition - c.emit(stmt, OpJump, preCondPos) - - // post-statement position - postStmtPos := len(c.currentInstructions()) - if postCondPos >= 0 { - c.changeOperand(postCondPos, postStmtPos) - } - - // update all break/continue jump positions - for _, pos := range loop.Breaks { - c.changeOperand(pos, postStmtPos) - } - for _, pos := range loop.Continues { - c.changeOperand(pos, postBodyPos) - } - - return nil -} - -func (c *Compiler) compileForInStmt(stmt *ast.ForInStmt) error { - c.symbolTable = c.symbolTable.Fork(true) - defer func() { - c.symbolTable = c.symbolTable.Parent(false) - }() - - // for-in statement is compiled like following: - // - // for :it := iterator(iterable); :it.next(); { - // k, v := :it.get() // DEFINE operator - // - // ... body ... - // } - // - // ":it" is a local variable but will be conflict with other user variables - // because character ":" is not allowed. - - // init - // :it = iterator(iterable) - itSymbol := c.symbolTable.Define(":it") - if err := c.Compile(stmt.Iterable); err != nil { - return err - } - c.emit(stmt, OpIteratorInit) - if itSymbol.Scope == ScopeGlobal { - c.emit(stmt, OpSetGlobal, itSymbol.Index) - } else { - c.emit(stmt, OpDefineLocal, itSymbol.Index) - } - - // pre-condition position - preCondPos := len(c.currentInstructions()) - - // condition - // :it.HasMore() - if itSymbol.Scope == ScopeGlobal { - c.emit(stmt, OpGetGlobal, itSymbol.Index) - } else { - c.emit(stmt, OpGetLocal, itSymbol.Index) - } - c.emit(stmt, OpIteratorNext) - - // condition jump position - postCondPos := c.emit(stmt, OpJumpFalsy, 0) - - // enter loop - loop := c.enterLoop() - - // assign key variable - if stmt.Key.Name != "_" { - keySymbol := c.symbolTable.Define(stmt.Key.Name) - if itSymbol.Scope == ScopeGlobal { - c.emit(stmt, OpGetGlobal, itSymbol.Index) - } else { - c.emit(stmt, OpGetLocal, itSymbol.Index) - } - c.emit(stmt, OpIteratorKey) - if keySymbol.Scope == ScopeGlobal { - c.emit(stmt, OpSetGlobal, keySymbol.Index) - } else { - c.emit(stmt, OpDefineLocal, keySymbol.Index) - } - } - - // assign value variable - if stmt.Value.Name != "_" { - valueSymbol := c.symbolTable.Define(stmt.Value.Name) - if itSymbol.Scope == ScopeGlobal { - c.emit(stmt, OpGetGlobal, itSymbol.Index) - } else { - c.emit(stmt, OpGetLocal, itSymbol.Index) - } - c.emit(stmt, OpIteratorValue) - if valueSymbol.Scope == ScopeGlobal { - c.emit(stmt, OpSetGlobal, valueSymbol.Index) - } else { - c.emit(stmt, OpDefineLocal, valueSymbol.Index) - } - } - - // body statement - if err := c.Compile(stmt.Body); err != nil { - c.leaveLoop() - return err - } - - c.leaveLoop() - - // post-body position - postBodyPos := len(c.currentInstructions()) - - // back to condition - c.emit(stmt, OpJump, preCondPos) - - // post-statement position - postStmtPos := len(c.currentInstructions()) - c.changeOperand(postCondPos, postStmtPos) - - // update all break/continue jump positions - for _, pos := range loop.Breaks { - c.changeOperand(pos, postStmtPos) - } - for _, pos := range loop.Continues { - c.changeOperand(pos, postBodyPos) - } - - return nil -} diff --git a/compiler/compiler_logical.go b/compiler/compiler_logical.go deleted file mode 100644 index 68c9675..0000000 --- a/compiler/compiler_logical.go +++ /dev/null @@ -1,30 +0,0 @@ -package compiler - -import ( - "github.com/d5/tengo/compiler/ast" - "github.com/d5/tengo/compiler/token" -) - -func (c *Compiler) compileLogical(node *ast.BinaryExpr) error { - // left side term - if err := c.Compile(node.LHS); err != nil { - return err - } - - // jump position - var jumpPos int - if node.Token == token.LAnd { - jumpPos = c.emit(node, OpAndJump, 0) - } else { - jumpPos = c.emit(node, OpOrJump, 0) - } - - // right side term - if err := c.Compile(node.RHS); err != nil { - return err - } - - c.changeOperand(jumpPos, len(c.currentInstructions())) - - return nil -} diff --git a/compiler/compiler_loops.go b/compiler/compiler_loops.go deleted file mode 100644 index 0659ce7..0000000 --- a/compiler/compiler_loops.go +++ /dev/null @@ -1,31 +0,0 @@ -package compiler - -func (c *Compiler) enterLoop() *Loop { - loop := &Loop{} - - c.loops = append(c.loops, loop) - c.loopIndex++ - - if c.trace != nil { - c.printTrace("LOOPE", c.loopIndex) - } - - return loop -} - -func (c *Compiler) leaveLoop() { - if c.trace != nil { - c.printTrace("LOOPL", c.loopIndex) - } - - c.loops = c.loops[:len(c.loops)-1] - c.loopIndex-- -} - -func (c *Compiler) currentLoop() *Loop { - if c.loopIndex >= 0 { - return c.loops[c.loopIndex] - } - - return nil -} diff --git a/compiler/compiler_module.go b/compiler/compiler_module.go deleted file mode 100644 index 8a3671c..0000000 --- a/compiler/compiler_module.go +++ /dev/null @@ -1,79 +0,0 @@ -package compiler - -import ( - "github.com/d5/tengo/compiler/ast" - "github.com/d5/tengo/compiler/parser" - "github.com/d5/tengo/objects" -) - -func (c *Compiler) checkCyclicImports(node ast.Node, modulePath string) error { - if c.modulePath == modulePath { - return c.errorf(node, "cyclic module import: %s", modulePath) - } else if c.parent != nil { - return c.parent.checkCyclicImports(node, modulePath) - } - - return nil -} - -func (c *Compiler) compileModule(node ast.Node, moduleName, modulePath string, src []byte) (*objects.CompiledFunction, error) { - if err := c.checkCyclicImports(node, modulePath); err != nil { - return nil, err - } - - compiledModule, exists := c.loadCompiledModule(modulePath) - if exists { - return compiledModule, nil - } - - modFile := c.file.Set().AddFile(moduleName, -1, len(src)) - p := parser.NewParser(modFile, src, nil) - file, err := p.ParseFile() - if err != nil { - return nil, err - } - - symbolTable := NewSymbolTable() - - // inherit builtin functions - for _, sym := range c.symbolTable.BuiltinSymbols() { - symbolTable.DefineBuiltin(sym.Index, sym.Name) - } - - // no global scope for the module - symbolTable = symbolTable.Fork(false) - - // compile module - moduleCompiler := c.fork(modFile, modulePath, symbolTable) - if err := moduleCompiler.Compile(file); err != nil { - return nil, err - } - - // code optimization - moduleCompiler.optimizeFunc(node) - - compiledFunc := moduleCompiler.Bytecode().MainFunction - compiledFunc.NumLocals = symbolTable.MaxSymbols() - - c.storeCompiledModule(modulePath, compiledFunc) - - return compiledFunc, nil -} - -func (c *Compiler) loadCompiledModule(modulePath string) (mod *objects.CompiledFunction, ok bool) { - if c.parent != nil { - return c.parent.loadCompiledModule(modulePath) - } - - mod, ok = c.compiledModules[modulePath] - - return -} - -func (c *Compiler) storeCompiledModule(modulePath string, module *objects.CompiledFunction) { - if c.parent != nil { - c.parent.storeCompiledModule(modulePath, module) - } - - c.compiledModules[modulePath] = module -} diff --git a/compiler/compiler_optimize_test.go b/compiler/compiler_optimize_test.go deleted file mode 100644 index c0764d4..0000000 --- a/compiler/compiler_optimize_test.go +++ /dev/null @@ -1,124 +0,0 @@ -package compiler_test - -import ( - "testing" - - "github.com/d5/tengo/compiler" -) - -func TestCompilerDeadCode(t *testing.T) { - expect(t, ` -func() { - a := 4 - return a - - b := 5 // dead code from here - c := a - return b -}`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 2), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - intObject(4), - intObject(5), - compiledFunction(0, 0, - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpDefineLocal, 0), - compiler.MakeInstruction(compiler.OpGetLocal, 0), - compiler.MakeInstruction(compiler.OpReturn, 1))))) - - expect(t, ` -func() { - if true { - return 5 - a := 4 // dead code from here - b := a - return b - } else { - return 4 - c := 5 // dead code from here - d := c - return d - } -}`, bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 2), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - intObject(5), - intObject(4), - compiledFunction(0, 0, - compiler.MakeInstruction(compiler.OpTrue), - compiler.MakeInstruction(compiler.OpJumpFalsy, 9), - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpReturn, 1), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpReturn, 1))))) - - expect(t, ` -func() { - a := 1 - for { - if a == 5 { - return 10 - } - 5 + 5 - return 20 - b := a - return b - } -}`, bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 4), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - intObject(1), - intObject(5), - intObject(10), - intObject(20), - compiledFunction(0, 0, - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpDefineLocal, 0), - compiler.MakeInstruction(compiler.OpGetLocal, 0), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpEqual), - compiler.MakeInstruction(compiler.OpJumpFalsy, 19), - compiler.MakeInstruction(compiler.OpConstant, 2), - compiler.MakeInstruction(compiler.OpReturn, 1), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpBinaryOp, 11), - compiler.MakeInstruction(compiler.OpPop), - compiler.MakeInstruction(compiler.OpConstant, 3), - compiler.MakeInstruction(compiler.OpReturn, 1))))) - - expect(t, ` -func() { - if true { - return 5 - a := 4 // dead code from here - b := a - return b - } else { - return 4 - c := 5 // dead code from here - d := c - return d - } -}`, bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 2), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - intObject(5), - intObject(4), - compiledFunction(0, 0, - compiler.MakeInstruction(compiler.OpTrue), - compiler.MakeInstruction(compiler.OpJumpFalsy, 9), - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpReturn, 1), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpReturn, 1))))) -} diff --git a/compiler/compiler_scopes.go b/compiler/compiler_scopes.go deleted file mode 100644 index b63f915..0000000 --- a/compiler/compiler_scopes.go +++ /dev/null @@ -1,43 +0,0 @@ -package compiler - -import "github.com/d5/tengo/compiler/source" - -func (c *Compiler) currentInstructions() []byte { - return c.scopes[c.scopeIndex].instructions -} - -func (c *Compiler) currentSourceMap() map[int]source.Pos { - return c.scopes[c.scopeIndex].sourceMap -} - -func (c *Compiler) enterScope() { - scope := CompilationScope{ - symbolInit: make(map[string]bool), - sourceMap: make(map[int]source.Pos), - } - - c.scopes = append(c.scopes, scope) - c.scopeIndex++ - - c.symbolTable = c.symbolTable.Fork(false) - - if c.trace != nil { - c.printTrace("SCOPE", c.scopeIndex) - } -} - -func (c *Compiler) leaveScope() (instructions []byte, sourceMap map[int]source.Pos) { - instructions = c.currentInstructions() - sourceMap = c.currentSourceMap() - - c.scopes = c.scopes[:len(c.scopes)-1] - c.scopeIndex-- - - c.symbolTable = c.symbolTable.Parent(true) - - if c.trace != nil { - c.printTrace("SCOPL", c.scopeIndex) - } - - return -} diff --git a/compiler/compiler_scopes_test.go b/compiler/compiler_scopes_test.go deleted file mode 100644 index 99c098b..0000000 --- a/compiler/compiler_scopes_test.go +++ /dev/null @@ -1,69 +0,0 @@ -package compiler_test - -import ( - "testing" - - "github.com/d5/tengo/compiler" -) - -func TestCompilerScopes(t *testing.T) { - expect(t, ` -if a := 1; a { - a = 2 - b := a -} else { - a = 3 - b := a -}`, bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpSetGlobal, 0), - compiler.MakeInstruction(compiler.OpGetGlobal, 0), - compiler.MakeInstruction(compiler.OpJumpFalsy, 27), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpSetGlobal, 0), - compiler.MakeInstruction(compiler.OpGetGlobal, 0), - compiler.MakeInstruction(compiler.OpSetGlobal, 1), - compiler.MakeInstruction(compiler.OpJump, 39), - compiler.MakeInstruction(compiler.OpConstant, 2), - compiler.MakeInstruction(compiler.OpSetGlobal, 0), - compiler.MakeInstruction(compiler.OpGetGlobal, 0), - compiler.MakeInstruction(compiler.OpSetGlobal, 1)), - objectsArray( - intObject(1), - intObject(2), - intObject(3)))) - - expect(t, ` -func() { - if a := 1; a { - a = 2 - b := a - } else { - a = 3 - b := a - } -}`, bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 3), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - intObject(1), - intObject(2), - intObject(3), - compiledFunction(0, 0, - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpDefineLocal, 0), - compiler.MakeInstruction(compiler.OpGetLocal, 0), - compiler.MakeInstruction(compiler.OpJumpFalsy, 22), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpSetLocal, 0), - compiler.MakeInstruction(compiler.OpGetLocal, 0), - compiler.MakeInstruction(compiler.OpDefineLocal, 1), - compiler.MakeInstruction(compiler.OpJump, 31), - compiler.MakeInstruction(compiler.OpConstant, 2), - compiler.MakeInstruction(compiler.OpSetLocal, 0), - compiler.MakeInstruction(compiler.OpGetLocal, 0), - compiler.MakeInstruction(compiler.OpDefineLocal, 1), - compiler.MakeInstruction(compiler.OpReturn, 0))))) -} diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go deleted file mode 100644 index 625f320..0000000 --- a/compiler/compiler_test.go +++ /dev/null @@ -1,1068 +0,0 @@ -package compiler_test - -import ( - "fmt" - "strings" - "testing" - - "github.com/d5/tengo/assert" - "github.com/d5/tengo/compiler" - "github.com/d5/tengo/compiler/parser" - "github.com/d5/tengo/compiler/source" - "github.com/d5/tengo/objects" -) - -func TestCompiler_Compile(t *testing.T) { - expect(t, `1 + 2`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpBinaryOp, 11), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - intObject(1), - intObject(2)))) - - expect(t, `1; 2`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpPop), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - intObject(1), - intObject(2)))) - - expect(t, `1 - 2`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpBinaryOp, 12), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - intObject(1), - intObject(2)))) - - expect(t, `1 * 2`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpBinaryOp, 13), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - intObject(1), - intObject(2)))) - - expect(t, `2 / 1`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpBinaryOp, 14), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - intObject(2), - intObject(1)))) - - expect(t, `true`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpTrue), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray())) - - expect(t, `false`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpFalse), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray())) - - expect(t, `1 > 2`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpBinaryOp, 39), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - intObject(1), - intObject(2)))) - - expect(t, `1 < 2`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpBinaryOp, 39), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - intObject(2), - intObject(1)))) - - expect(t, `1 >= 2`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpBinaryOp, 44), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - intObject(1), - intObject(2)))) - - expect(t, `1 <= 2`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpBinaryOp, 44), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - intObject(2), - intObject(1)))) - - expect(t, `1 == 2`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpEqual), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - intObject(1), - intObject(2)))) - - expect(t, `1 != 2`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpNotEqual), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - intObject(1), - intObject(2)))) - - expect(t, `true == false`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpTrue), - compiler.MakeInstruction(compiler.OpFalse), - compiler.MakeInstruction(compiler.OpEqual), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray())) - - expect(t, `true != false`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpTrue), - compiler.MakeInstruction(compiler.OpFalse), - compiler.MakeInstruction(compiler.OpNotEqual), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray())) - - expect(t, `-1`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpMinus), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - intObject(1)))) - - expect(t, `!true`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpTrue), - compiler.MakeInstruction(compiler.OpLNot), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray())) - - expect(t, `if true { 10 }; 3333`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpTrue), // 0000 - compiler.MakeInstruction(compiler.OpJumpFalsy, 8), // 0001 - compiler.MakeInstruction(compiler.OpConstant, 0), // 0004 - compiler.MakeInstruction(compiler.OpPop), // 0007 - compiler.MakeInstruction(compiler.OpConstant, 1), // 0008 - compiler.MakeInstruction(compiler.OpPop)), // 0011 - objectsArray( - intObject(10), - intObject(3333)))) - - expect(t, `if (true) { 10 } else { 20 }; 3333;`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpTrue), // 0000 - compiler.MakeInstruction(compiler.OpJumpFalsy, 11), // 0001 - compiler.MakeInstruction(compiler.OpConstant, 0), // 0004 - compiler.MakeInstruction(compiler.OpPop), // 0007 - compiler.MakeInstruction(compiler.OpJump, 15), // 0008 - compiler.MakeInstruction(compiler.OpConstant, 1), // 0011 - compiler.MakeInstruction(compiler.OpPop), // 0014 - compiler.MakeInstruction(compiler.OpConstant, 2), // 0015 - compiler.MakeInstruction(compiler.OpPop)), // 0018 - objectsArray( - intObject(10), - intObject(20), - intObject(3333)))) - - expect(t, `"kami"`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - stringObject("kami")))) - - expect(t, `"ka" + "mi"`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpBinaryOp, 11), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - stringObject("ka"), - stringObject("mi")))) - - expect(t, `a := 1; b := 2; a += b`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpSetGlobal, 0), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpSetGlobal, 1), - compiler.MakeInstruction(compiler.OpGetGlobal, 0), - compiler.MakeInstruction(compiler.OpGetGlobal, 1), - compiler.MakeInstruction(compiler.OpBinaryOp, 11), - compiler.MakeInstruction(compiler.OpSetGlobal, 0)), - objectsArray( - intObject(1), - intObject(2)))) - - expect(t, `a := 1; b := 2; a /= b`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpSetGlobal, 0), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpSetGlobal, 1), - compiler.MakeInstruction(compiler.OpGetGlobal, 0), - compiler.MakeInstruction(compiler.OpGetGlobal, 1), - compiler.MakeInstruction(compiler.OpBinaryOp, 14), - compiler.MakeInstruction(compiler.OpSetGlobal, 0)), - objectsArray( - intObject(1), - intObject(2)))) - - expect(t, `[]`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpArray, 0), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray())) - - expect(t, `[1, 2, 3]`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpConstant, 2), - compiler.MakeInstruction(compiler.OpArray, 3), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - intObject(1), - intObject(2), - intObject(3)))) - - expect(t, `[1 + 2, 3 - 4, 5 * 6]`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpBinaryOp, 11), - compiler.MakeInstruction(compiler.OpConstant, 2), - compiler.MakeInstruction(compiler.OpConstant, 3), - compiler.MakeInstruction(compiler.OpBinaryOp, 12), - compiler.MakeInstruction(compiler.OpConstant, 4), - compiler.MakeInstruction(compiler.OpConstant, 5), - compiler.MakeInstruction(compiler.OpBinaryOp, 13), - compiler.MakeInstruction(compiler.OpArray, 3), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - intObject(1), - intObject(2), - intObject(3), - intObject(4), - intObject(5), - intObject(6)))) - - expect(t, `{}`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpMap, 0), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray())) - - expect(t, `{a: 2, b: 4, c: 6}`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpConstant, 2), - compiler.MakeInstruction(compiler.OpConstant, 3), - compiler.MakeInstruction(compiler.OpConstant, 4), - compiler.MakeInstruction(compiler.OpConstant, 5), - compiler.MakeInstruction(compiler.OpMap, 6), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - stringObject("a"), - intObject(2), - stringObject("b"), - intObject(4), - stringObject("c"), - intObject(6)))) - - expect(t, `{a: 2 + 3, b: 5 * 6}`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpConstant, 2), - compiler.MakeInstruction(compiler.OpBinaryOp, 11), - compiler.MakeInstruction(compiler.OpConstant, 3), - compiler.MakeInstruction(compiler.OpConstant, 4), - compiler.MakeInstruction(compiler.OpConstant, 5), - compiler.MakeInstruction(compiler.OpBinaryOp, 13), - compiler.MakeInstruction(compiler.OpMap, 4), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - stringObject("a"), - intObject(2), - intObject(3), - stringObject("b"), - intObject(5), - intObject(6)))) - - expect(t, `[1, 2, 3][1 + 1]`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpConstant, 2), - compiler.MakeInstruction(compiler.OpArray, 3), - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpBinaryOp, 11), - compiler.MakeInstruction(compiler.OpIndex), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - intObject(1), - intObject(2), - intObject(3)))) - - expect(t, `{a: 2}[2 - 1]`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpMap, 2), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpConstant, 2), - compiler.MakeInstruction(compiler.OpBinaryOp, 12), - compiler.MakeInstruction(compiler.OpIndex), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - stringObject("a"), - intObject(2), - intObject(1)))) - - expect(t, `[1, 2, 3][:]`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpConstant, 2), - compiler.MakeInstruction(compiler.OpArray, 3), - compiler.MakeInstruction(compiler.OpNull), - compiler.MakeInstruction(compiler.OpNull), - compiler.MakeInstruction(compiler.OpSliceIndex), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - intObject(1), - intObject(2), - intObject(3)))) - - expect(t, `[1, 2, 3][0 : 2]`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpConstant, 2), - compiler.MakeInstruction(compiler.OpArray, 3), - compiler.MakeInstruction(compiler.OpConstant, 3), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpSliceIndex), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - intObject(1), - intObject(2), - intObject(3), - intObject(0)))) - - expect(t, `[1, 2, 3][:2]`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpConstant, 2), - compiler.MakeInstruction(compiler.OpArray, 3), - compiler.MakeInstruction(compiler.OpNull), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpSliceIndex), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - intObject(1), - intObject(2), - intObject(3)))) - - expect(t, `[1, 2, 3][0:]`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpConstant, 2), - compiler.MakeInstruction(compiler.OpArray, 3), - compiler.MakeInstruction(compiler.OpConstant, 3), - compiler.MakeInstruction(compiler.OpNull), - compiler.MakeInstruction(compiler.OpSliceIndex), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - intObject(1), - intObject(2), - intObject(3), - intObject(0)))) - - expect(t, `func() { return 5 + 10 }`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 2), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - intObject(5), - intObject(10), - compiledFunction(0, 0, - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpBinaryOp, 11), - compiler.MakeInstruction(compiler.OpReturn, 1))))) - - expect(t, `func() { 5 + 10 }`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 2), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - intObject(5), - intObject(10), - compiledFunction(0, 0, - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpBinaryOp, 11), - compiler.MakeInstruction(compiler.OpPop), - compiler.MakeInstruction(compiler.OpReturn, 0))))) - - expect(t, `func() { 1; 2 }`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 2), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - intObject(1), - intObject(2), - compiledFunction(0, 0, - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpPop), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpPop), - compiler.MakeInstruction(compiler.OpReturn, 0))))) - - expect(t, `func() { 1; return 2 }`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 2), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - intObject(1), - intObject(2), - compiledFunction(0, 0, - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpPop), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpReturn, 1))))) - - expect(t, `func() { if(true) { return 1 } else { return 2 } }`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 2), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - intObject(1), - intObject(2), - compiledFunction(0, 0, - compiler.MakeInstruction(compiler.OpTrue), // 0000 - compiler.MakeInstruction(compiler.OpJumpFalsy, 9), // 0001 - compiler.MakeInstruction(compiler.OpConstant, 0), // 0004 - compiler.MakeInstruction(compiler.OpReturn, 1), // 0007 - compiler.MakeInstruction(compiler.OpConstant, 1), // 0009 - compiler.MakeInstruction(compiler.OpReturn, 1))))) // 0012 - - expect(t, `func() { 1; if(true) { 2 } else { 3 }; 4 }`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 4), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - intObject(1), - intObject(2), - intObject(3), - intObject(4), - compiledFunction(0, 0, - compiler.MakeInstruction(compiler.OpConstant, 0), // 0000 - compiler.MakeInstruction(compiler.OpPop), // 0003 - compiler.MakeInstruction(compiler.OpTrue), // 0004 - compiler.MakeInstruction(compiler.OpJumpFalsy, 15), // 0005 - compiler.MakeInstruction(compiler.OpConstant, 1), // 0008 - compiler.MakeInstruction(compiler.OpPop), // 0011 - compiler.MakeInstruction(compiler.OpJump, 19), // 0012 - compiler.MakeInstruction(compiler.OpConstant, 2), // 0015 - compiler.MakeInstruction(compiler.OpPop), // 0018 - compiler.MakeInstruction(compiler.OpConstant, 3), // 0019 - compiler.MakeInstruction(compiler.OpPop), // 0022 - compiler.MakeInstruction(compiler.OpReturn, 0))))) // 0023 - - expect(t, `func() { }`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - compiledFunction(0, 0, compiler.MakeInstruction(compiler.OpReturn, 0))))) - - expect(t, `func() { 24 }()`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpCall, 0), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - intObject(24), - compiledFunction(0, 0, - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpPop), - compiler.MakeInstruction(compiler.OpReturn, 0))))) - - expect(t, `func() { return 24 }()`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpCall, 0), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - intObject(24), - compiledFunction(0, 0, - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpReturn, 1))))) - - expect(t, `noArg := func() { 24 }; noArg();`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpSetGlobal, 0), - compiler.MakeInstruction(compiler.OpGetGlobal, 0), - compiler.MakeInstruction(compiler.OpCall, 0), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - intObject(24), - compiledFunction(0, 0, - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpPop), - compiler.MakeInstruction(compiler.OpReturn, 0))))) - - expect(t, `noArg := func() { return 24 }; noArg();`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpSetGlobal, 0), - compiler.MakeInstruction(compiler.OpGetGlobal, 0), - compiler.MakeInstruction(compiler.OpCall, 0), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - intObject(24), - compiledFunction(0, 0, - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpReturn, 1))))) - - expect(t, `n := 55; func() { n };`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpSetGlobal, 0), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - intObject(55), - compiledFunction(0, 0, - compiler.MakeInstruction(compiler.OpGetGlobal, 0), - compiler.MakeInstruction(compiler.OpPop), - compiler.MakeInstruction(compiler.OpReturn, 0))))) - - expect(t, `func() { n := 55; return n }`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - intObject(55), - compiledFunction(1, 0, - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpDefineLocal, 0), - compiler.MakeInstruction(compiler.OpGetLocal, 0), - compiler.MakeInstruction(compiler.OpReturn, 1))))) - - expect(t, `func() { a := 55; b := 77; return a + b }`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 2), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - intObject(55), - intObject(77), - compiledFunction(2, 0, - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpDefineLocal, 0), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpDefineLocal, 1), - compiler.MakeInstruction(compiler.OpGetLocal, 0), - compiler.MakeInstruction(compiler.OpGetLocal, 1), - compiler.MakeInstruction(compiler.OpBinaryOp, 11), - compiler.MakeInstruction(compiler.OpReturn, 1))))) - - expect(t, `f1 := func(a) { return a }; f1(24);`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpSetGlobal, 0), - compiler.MakeInstruction(compiler.OpGetGlobal, 0), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpCall, 1), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - compiledFunction(1, 1, - compiler.MakeInstruction(compiler.OpGetLocal, 0), - compiler.MakeInstruction(compiler.OpReturn, 1)), - intObject(24)))) - - expect(t, `varTest := func(...a) { return a }; varTest(1,2,3);`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpSetGlobal, 0), - compiler.MakeInstruction(compiler.OpGetGlobal, 0), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpConstant, 2), - compiler.MakeInstruction(compiler.OpConstant, 3), - compiler.MakeInstruction(compiler.OpCall, 3), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - compiledFunction(1, 1, - compiler.MakeInstruction(compiler.OpGetLocal, 0), - compiler.MakeInstruction(compiler.OpReturn, 1)), - intObject(1), intObject(2), intObject(3)))) - - expect(t, `f1 := func(a, b, c) { a; b; return c; }; f1(24, 25, 26);`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpSetGlobal, 0), - compiler.MakeInstruction(compiler.OpGetGlobal, 0), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpConstant, 2), - compiler.MakeInstruction(compiler.OpConstant, 3), - compiler.MakeInstruction(compiler.OpCall, 3), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - compiledFunction(3, 3, - compiler.MakeInstruction(compiler.OpGetLocal, 0), - compiler.MakeInstruction(compiler.OpPop), - compiler.MakeInstruction(compiler.OpGetLocal, 1), - compiler.MakeInstruction(compiler.OpPop), - compiler.MakeInstruction(compiler.OpGetLocal, 2), - compiler.MakeInstruction(compiler.OpReturn, 1)), - intObject(24), - intObject(25), - intObject(26)))) - - expect(t, `func() { n := 55; n = 23; return n }`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 2), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - intObject(55), - intObject(23), - compiledFunction(1, 0, - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpDefineLocal, 0), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpSetLocal, 0), - compiler.MakeInstruction(compiler.OpGetLocal, 0), - compiler.MakeInstruction(compiler.OpReturn, 1))))) - expect(t, `len([]);`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpGetBuiltin, 0), - compiler.MakeInstruction(compiler.OpArray, 0), - compiler.MakeInstruction(compiler.OpCall, 1), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray())) - - expect(t, `func() { return len([]) }`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - compiledFunction(0, 0, - compiler.MakeInstruction(compiler.OpGetBuiltin, 0), - compiler.MakeInstruction(compiler.OpArray, 0), - compiler.MakeInstruction(compiler.OpCall, 1), - compiler.MakeInstruction(compiler.OpReturn, 1))))) - - expect(t, `func(a) { func(b) { return a + b } }`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - compiledFunction(1, 1, - compiler.MakeInstruction(compiler.OpGetFree, 0), - compiler.MakeInstruction(compiler.OpGetLocal, 0), - compiler.MakeInstruction(compiler.OpBinaryOp, 11), - compiler.MakeInstruction(compiler.OpReturn, 1)), - compiledFunction(1, 1, - compiler.MakeInstruction(compiler.OpGetLocalPtr, 0), - compiler.MakeInstruction(compiler.OpClosure, 0, 1), - compiler.MakeInstruction(compiler.OpPop), - compiler.MakeInstruction(compiler.OpReturn, 0))))) - - expect(t, ` -func(a) { - return func(b) { - return func(c) { - return a + b + c - } - } -}`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 2), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - compiledFunction(1, 1, - compiler.MakeInstruction(compiler.OpGetFree, 0), - compiler.MakeInstruction(compiler.OpGetFree, 1), - compiler.MakeInstruction(compiler.OpBinaryOp, 11), - compiler.MakeInstruction(compiler.OpGetLocal, 0), - compiler.MakeInstruction(compiler.OpBinaryOp, 11), - compiler.MakeInstruction(compiler.OpReturn, 1)), - compiledFunction(1, 1, - compiler.MakeInstruction(compiler.OpGetFreePtr, 0), - compiler.MakeInstruction(compiler.OpGetLocalPtr, 0), - compiler.MakeInstruction(compiler.OpClosure, 0, 2), - compiler.MakeInstruction(compiler.OpReturn, 1)), - compiledFunction(1, 1, - compiler.MakeInstruction(compiler.OpGetLocalPtr, 0), - compiler.MakeInstruction(compiler.OpClosure, 1, 1), - compiler.MakeInstruction(compiler.OpReturn, 1))))) - - expect(t, ` -g := 55; - -func() { - a := 66; - - return func() { - b := 77; - - return func() { - c := 88; - - return g + a + b + c; - } - } -}`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpSetGlobal, 0), - compiler.MakeInstruction(compiler.OpConstant, 6), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - intObject(55), - intObject(66), - intObject(77), - intObject(88), - compiledFunction(1, 0, - compiler.MakeInstruction(compiler.OpConstant, 3), - compiler.MakeInstruction(compiler.OpDefineLocal, 0), - compiler.MakeInstruction(compiler.OpGetGlobal, 0), - compiler.MakeInstruction(compiler.OpGetFree, 0), - compiler.MakeInstruction(compiler.OpBinaryOp, 11), - compiler.MakeInstruction(compiler.OpGetFree, 1), - compiler.MakeInstruction(compiler.OpBinaryOp, 11), - compiler.MakeInstruction(compiler.OpGetLocal, 0), - compiler.MakeInstruction(compiler.OpBinaryOp, 11), - compiler.MakeInstruction(compiler.OpReturn, 1)), - compiledFunction(1, 0, - compiler.MakeInstruction(compiler.OpConstant, 2), - compiler.MakeInstruction(compiler.OpDefineLocal, 0), - compiler.MakeInstruction(compiler.OpGetFreePtr, 0), - compiler.MakeInstruction(compiler.OpGetLocalPtr, 0), - compiler.MakeInstruction(compiler.OpClosure, 4, 2), - compiler.MakeInstruction(compiler.OpReturn, 1)), - compiledFunction(1, 0, - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpDefineLocal, 0), - compiler.MakeInstruction(compiler.OpGetLocalPtr, 0), - compiler.MakeInstruction(compiler.OpClosure, 5, 1), - compiler.MakeInstruction(compiler.OpReturn, 1))))) - - expect(t, `for i:=0; i<10; i++ {}`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpSetGlobal, 0), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpGetGlobal, 0), - compiler.MakeInstruction(compiler.OpBinaryOp, 39), - compiler.MakeInstruction(compiler.OpJumpFalsy, 31), - compiler.MakeInstruction(compiler.OpGetGlobal, 0), - compiler.MakeInstruction(compiler.OpConstant, 2), - compiler.MakeInstruction(compiler.OpBinaryOp, 11), - compiler.MakeInstruction(compiler.OpSetGlobal, 0), - compiler.MakeInstruction(compiler.OpJump, 6)), - objectsArray( - intObject(0), - intObject(10), - intObject(1)))) - - expect(t, `m := {}; for k, v in m {}`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpMap, 0), - compiler.MakeInstruction(compiler.OpSetGlobal, 0), - compiler.MakeInstruction(compiler.OpGetGlobal, 0), - compiler.MakeInstruction(compiler.OpIteratorInit), - compiler.MakeInstruction(compiler.OpSetGlobal, 1), - compiler.MakeInstruction(compiler.OpGetGlobal, 1), - compiler.MakeInstruction(compiler.OpIteratorNext), - compiler.MakeInstruction(compiler.OpJumpFalsy, 37), - compiler.MakeInstruction(compiler.OpGetGlobal, 1), - compiler.MakeInstruction(compiler.OpIteratorKey), - compiler.MakeInstruction(compiler.OpSetGlobal, 2), - compiler.MakeInstruction(compiler.OpGetGlobal, 1), - compiler.MakeInstruction(compiler.OpIteratorValue), - compiler.MakeInstruction(compiler.OpSetGlobal, 3), - compiler.MakeInstruction(compiler.OpJump, 13)), - objectsArray())) - - expect(t, `a := 0; a == 0 && a != 1 || a < 1`, - bytecode( - concat( - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpSetGlobal, 0), - compiler.MakeInstruction(compiler.OpGetGlobal, 0), - compiler.MakeInstruction(compiler.OpConstant, 0), - compiler.MakeInstruction(compiler.OpEqual), - compiler.MakeInstruction(compiler.OpAndJump, 23), - compiler.MakeInstruction(compiler.OpGetGlobal, 0), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpNotEqual), - compiler.MakeInstruction(compiler.OpOrJump, 34), - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpGetGlobal, 0), - compiler.MakeInstruction(compiler.OpBinaryOp, 39), - compiler.MakeInstruction(compiler.OpPop)), - objectsArray( - intObject(0), - intObject(1)))) - - expectError(t, `import("user1")`, "module 'user1' not found") // unknown module name - - expectError(t, ` -r["x"] = { - @a:1, - @b:1, - @c:1, - @d:1, - @e:1, - @f:1, - @g:1, - @h:1, - @i:1, - @j:1, - @k:1 -} -`, "Parse Error: illegal character U+0040 '@'\n\tat test:3:5 (and 10 more errors)") // too many errors - - expectError(t, `import("")`, "empty module name") -} - -func concat(instructions ...[]byte) []byte { - var concat []byte - for _, i := range instructions { - concat = append(concat, i...) - } - - return concat -} - -func bytecode(instructions []byte, constants []objects.Object) *compiler.Bytecode { - return &compiler.Bytecode{ - FileSet: source.NewFileSet(), - MainFunction: &objects.CompiledFunction{Instructions: instructions}, - Constants: constants, - } -} - -func expect(t *testing.T, input string, expected *compiler.Bytecode) (ok bool) { - actual, trace, err := traceCompile(input, nil) - - defer func() { - if !ok { - for _, tr := range trace { - t.Log(tr) - } - } - }() - - if !assert.NoError(t, err) { - return - } - - ok = equalBytecode(t, expected, actual) - - return -} - -func expectError(t *testing.T, input, expected string) (ok bool) { - _, trace, err := traceCompile(input, nil) - - defer func() { - if !ok { - for _, tr := range trace { - t.Log(tr) - } - } - }() - - if !assert.Error(t, err) { - return - } - - if !assert.True(t, strings.Contains(err.Error(), expected), "expected error string: %s, got: %s", expected, err.Error()) { - return - } - - ok = true - - return -} - -func equalBytecode(t *testing.T, expected, actual *compiler.Bytecode) bool { - return assert.Equal(t, expected.MainFunction, actual.MainFunction) && - equalConstants(t, expected.Constants, actual.Constants) -} - -func equalConstants(t *testing.T, expected, actual []objects.Object) bool { - if !assert.Equal(t, len(expected), len(actual)) { - return false - } - - for i := 0; i < len(expected); i++ { - if !assert.Equal(t, expected[i], actual[i]) { - return false - } - } - - return true -} - -type tracer struct { - Out []string -} - -func (o *tracer) Write(p []byte) (n int, err error) { - o.Out = append(o.Out, string(p)) - return len(p), nil -} - -func traceCompile(input string, symbols map[string]objects.Object) (res *compiler.Bytecode, trace []string, err error) { - fileSet := source.NewFileSet() - file := fileSet.AddFile("test", -1, len(input)) - - p := parser.NewParser(file, []byte(input), nil) - - symTable := compiler.NewSymbolTable() - for name := range symbols { - symTable.Define(name) - } - for idx, fn := range objects.Builtins { - symTable.DefineBuiltin(idx, fn.Name) - } - - tr := &tracer{} - c := compiler.NewCompiler(file, symTable, nil, nil, tr) - parsed, err := p.ParseFile() - if err != nil { - return - } - - err = c.Compile(parsed) - res = c.Bytecode() - res.RemoveDuplicates() - { - trace = append(trace, fmt.Sprintf("Compiler Trace:\n%s", strings.Join(tr.Out, ""))) - trace = append(trace, fmt.Sprintf("Compiled Constants:\n%s", strings.Join(res.FormatConstants(), "\n"))) - trace = append(trace, fmt.Sprintf("Compiled Instructions:\n%s\n", strings.Join(res.FormatInstructions(), "\n"))) - } - if err != nil { - return - } - - return -} - -func objectsArray(o ...objects.Object) []objects.Object { - return o -} - -func intObject(v int64) *objects.Int { - return &objects.Int{Value: v} -} - -func stringObject(v string) *objects.String { - return &objects.String{Value: v} -} - -func compiledFunction(numLocals, numParams int, insts ...[]byte) *objects.CompiledFunction { - return &objects.CompiledFunction{Instructions: concat(insts...), NumLocals: numLocals, NumParameters: numParams} -} diff --git a/compiler/emitted_instruction.go b/compiler/emitted_instruction.go deleted file mode 100644 index 8572fb0..0000000 --- a/compiler/emitted_instruction.go +++ /dev/null @@ -1,8 +0,0 @@ -package compiler - -// EmittedInstruction represents an opcode -// with its emitted position. -type EmittedInstruction struct { - Opcode Opcode - Position int -} diff --git a/compiler/error.go b/compiler/error.go deleted file mode 100644 index 6ac33d4..0000000 --- a/compiler/error.go +++ /dev/null @@ -1,20 +0,0 @@ -package compiler - -import ( - "fmt" - - "github.com/d5/tengo/compiler/ast" - "github.com/d5/tengo/compiler/source" -) - -// Error represents a compiler error. -type Error struct { - fileSet *source.FileSet - node ast.Node - error error -} - -func (e *Error) Error() string { - filePos := e.fileSet.Position(e.node.Pos()) - return fmt.Sprintf("Compile Error: %s\n\tat %s", e.error.Error(), filePos) -} diff --git a/compiler/instructions_test.go b/compiler/instructions_test.go deleted file mode 100644 index 9652c49..0000000 --- a/compiler/instructions_test.go +++ /dev/null @@ -1,65 +0,0 @@ -package compiler_test - -import ( - "strings" - "testing" - - "github.com/d5/tengo/assert" - "github.com/d5/tengo/compiler" -) - -func TestInstructions_String(t *testing.T) { - assertInstructionString(t, - [][]byte{ - compiler.MakeInstruction(compiler.OpConstant, 1), - compiler.MakeInstruction(compiler.OpConstant, 2), - compiler.MakeInstruction(compiler.OpConstant, 65535), - }, - `0000 CONST 1 -0003 CONST 2 -0006 CONST 65535`) - - assertInstructionString(t, - [][]byte{ - compiler.MakeInstruction(compiler.OpBinaryOp, 11), - compiler.MakeInstruction(compiler.OpConstant, 2), - compiler.MakeInstruction(compiler.OpConstant, 65535), - }, - `0000 BINARYOP 11 -0002 CONST 2 -0005 CONST 65535`) - - assertInstructionString(t, - [][]byte{ - compiler.MakeInstruction(compiler.OpBinaryOp, 11), - compiler.MakeInstruction(compiler.OpGetLocal, 1), - compiler.MakeInstruction(compiler.OpConstant, 2), - compiler.MakeInstruction(compiler.OpConstant, 65535), - }, - `0000 BINARYOP 11 -0002 GETL 1 -0004 CONST 2 -0007 CONST 65535`) -} - -func TestMakeInstruction(t *testing.T) { - makeInstruction(t, []byte{byte(compiler.OpConstant), 0, 0}, compiler.OpConstant, 0) - makeInstruction(t, []byte{byte(compiler.OpConstant), 0, 1}, compiler.OpConstant, 1) - makeInstruction(t, []byte{byte(compiler.OpConstant), 255, 254}, compiler.OpConstant, 65534) - makeInstruction(t, []byte{byte(compiler.OpPop)}, compiler.OpPop) - makeInstruction(t, []byte{byte(compiler.OpTrue)}, compiler.OpTrue) - makeInstruction(t, []byte{byte(compiler.OpFalse)}, compiler.OpFalse) -} - -func assertInstructionString(t *testing.T, instructions [][]byte, expected string) { - concatted := make([]byte, 0) - for _, e := range instructions { - concatted = append(concatted, e...) - } - assert.Equal(t, expected, strings.Join(compiler.FormatInstructions(concatted, 0), "\n")) -} - -func makeInstruction(t *testing.T, expected []byte, opcode compiler.Opcode, operands ...int) { - inst := compiler.MakeInstruction(opcode, operands...) - assert.Equal(t, expected, []byte(inst)) -} diff --git a/compiler/loop.go b/compiler/loop.go deleted file mode 100644 index e27cb09..0000000 --- a/compiler/loop.go +++ /dev/null @@ -1,8 +0,0 @@ -package compiler - -// Loop represents a loop construct that -// the compiler uses to track the current loop. -type Loop struct { - Continues []int - Breaks []int -} diff --git a/compiler/module_loader.go b/compiler/module_loader.go deleted file mode 100644 index b050474..0000000 --- a/compiler/module_loader.go +++ /dev/null @@ -1,4 +0,0 @@ -package compiler - -// ModuleLoader should take a module name and return the module data. -type ModuleLoader func(moduleName string) ([]byte, error) diff --git a/compiler/opcodes_test.go b/compiler/opcodes_test.go deleted file mode 100644 index 2ac868d..0000000 --- a/compiler/opcodes_test.go +++ /dev/null @@ -1,20 +0,0 @@ -package compiler_test - -import ( - "testing" - - "github.com/d5/tengo/assert" - "github.com/d5/tengo/compiler" -) - -func TestReadOperands(t *testing.T) { - assertReadOperand(t, compiler.OpConstant, []int{65535}, 2) -} - -func assertReadOperand(t *testing.T, opcode compiler.Opcode, operands []int, expectedBytes int) { - inst := compiler.MakeInstruction(opcode, operands...) - numOperands := compiler.OpcodeOperands[opcode] - operandsRead, read := compiler.ReadOperands(numOperands, inst[1:]) - assert.Equal(t, expectedBytes, read) - assert.Equal(t, operands, operandsRead) -} diff --git a/compiler/parser/error.go b/compiler/parser/error.go deleted file mode 100644 index 80544fb..0000000 --- a/compiler/parser/error.go +++ /dev/null @@ -1,21 +0,0 @@ -package parser - -import ( - "fmt" - - "github.com/d5/tengo/compiler/source" -) - -// Error represents a parser error. -type Error struct { - Pos source.FilePos - Msg string -} - -func (e Error) Error() string { - if e.Pos.Filename != "" || e.Pos.IsValid() { - return fmt.Sprintf("Parse Error: %s\n\tat %s", e.Msg, e.Pos) - } - - return fmt.Sprintf("Parse Error: %s", e.Msg) -} diff --git a/compiler/parser/error_list.go b/compiler/parser/error_list.go deleted file mode 100644 index 599eaf7..0000000 --- a/compiler/parser/error_list.go +++ /dev/null @@ -1,68 +0,0 @@ -package parser - -import ( - "fmt" - "sort" - - "github.com/d5/tengo/compiler/source" -) - -// ErrorList is a collection of parser errors. -type ErrorList []*Error - -// Add adds a new parser error to the collection. -func (p *ErrorList) Add(pos source.FilePos, msg string) { - *p = append(*p, &Error{pos, msg}) -} - -// Len returns the number of elements in the collection. -func (p ErrorList) Len() int { - return len(p) -} - -func (p ErrorList) Swap(i, j int) { - p[i], p[j] = p[j], p[i] -} - -func (p ErrorList) Less(i, j int) bool { - e := &p[i].Pos - f := &p[j].Pos - - if e.Filename != f.Filename { - return e.Filename < f.Filename - } - - if e.Line != f.Line { - return e.Line < f.Line - } - - if e.Column != f.Column { - return e.Column < f.Column - } - - return p[i].Msg < p[j].Msg -} - -// Sort sorts the collection. -func (p ErrorList) Sort() { - sort.Sort(p) -} - -func (p ErrorList) Error() string { - switch len(p) { - case 0: - return "no errors" - case 1: - return p[0].Error() - } - return fmt.Sprintf("%s (and %d more errors)", p[0], len(p)-1) -} - -// Err returns an error. -func (p ErrorList) Err() error { - if len(p) == 0 { - return nil - } - - return p -} diff --git a/compiler/parser/error_list_test.go b/compiler/parser/error_list_test.go deleted file mode 100644 index 926dfdb..0000000 --- a/compiler/parser/error_list_test.go +++ /dev/null @@ -1,18 +0,0 @@ -package parser_test - -import ( - "testing" - - "github.com/d5/tengo/assert" - "github.com/d5/tengo/compiler/parser" - "github.com/d5/tengo/compiler/source" -) - -func TestErrorList_Sort(t *testing.T) { - var list parser.ErrorList - list.Add(source.FilePos{Offset: 20, Line: 2, Column: 10}, "error 2") - list.Add(source.FilePos{Offset: 30, Line: 3, Column: 10}, "error 3") - list.Add(source.FilePos{Offset: 10, Line: 1, Column: 10}, "error 1") - list.Sort() - assert.Equal(t, "Parse Error: error 1\n\tat 1:10 (and 2 more errors)", list.Error()) -} diff --git a/compiler/parser/error_test.go b/compiler/parser/error_test.go deleted file mode 100644 index a86c0e5..0000000 --- a/compiler/parser/error_test.go +++ /dev/null @@ -1,14 +0,0 @@ -package parser_test - -import ( - "testing" - - "github.com/d5/tengo/assert" - "github.com/d5/tengo/compiler/parser" - "github.com/d5/tengo/compiler/source" -) - -func TestError_Error(t *testing.T) { - err := &parser.Error{Pos: source.FilePos{Offset: 10, Line: 1, Column: 10}, Msg: "test"} - assert.Equal(t, "Parse Error: test\n\tat 1:10", err.Error()) -} diff --git a/compiler/parser/parse_source.go b/compiler/parser/parse_source.go deleted file mode 100644 index 5c71436..0000000 --- a/compiler/parser/parse_source.go +++ /dev/null @@ -1,17 +0,0 @@ -package parser - -import ( - "io" - - "github.com/d5/tengo/compiler/ast" - "github.com/d5/tengo/compiler/source" -) - -// ParseSource parses source code 'src' and builds an AST. -func ParseSource(filename string, src []byte, trace io.Writer) (res *ast.File, err error) { - fileSet := source.NewFileSet() - file := fileSet.AddFile(filename, -1, len(src)) - - p := NewParser(file, src, trace) - return p.ParseFile() -} diff --git a/compiler/parser/parser_array_test.go b/compiler/parser/parser_array_test.go deleted file mode 100644 index 720c705..0000000 --- a/compiler/parser/parser_array_test.go +++ /dev/null @@ -1,106 +0,0 @@ -package parser_test - -import ( - "testing" - - "github.com/d5/tengo/compiler/ast" - "github.com/d5/tengo/compiler/token" -) - -func TestArray(t *testing.T) { - expect(t, "[1, 2, 3]", func(p pfn) []ast.Stmt { - return stmts( - exprStmt( - arrayLit(p(1, 1), p(1, 9), - intLit(1, p(1, 2)), - intLit(2, p(1, 5)), - intLit(3, p(1, 8))))) - }) - - expect(t, ` -[ - 1, - 2, - 3 -]`, func(p pfn) []ast.Stmt { - return stmts( - exprStmt( - arrayLit(p(2, 1), p(6, 1), - intLit(1, p(3, 2)), - intLit(2, p(4, 2)), - intLit(3, p(5, 2))))) - }) - expect(t, ` -[ - 1, - 2, - 3 - -]`, func(p pfn) []ast.Stmt { - return stmts( - exprStmt( - arrayLit(p(2, 1), p(7, 1), - intLit(1, p(3, 2)), - intLit(2, p(4, 2)), - intLit(3, p(5, 2))))) - }) - - expect(t, `[1, "foo", 12.34]`, func(p pfn) []ast.Stmt { - return stmts( - exprStmt( - arrayLit(p(1, 1), p(1, 17), - intLit(1, p(1, 2)), - stringLit("foo", p(1, 5)), - floatLit(12.34, p(1, 12))))) - }) - - expect(t, "a = [1, 2, 3]", func(p pfn) []ast.Stmt { - return stmts( - assignStmt( - exprs(ident("a", p(1, 1))), - exprs(arrayLit(p(1, 5), p(1, 13), - intLit(1, p(1, 6)), - intLit(2, p(1, 9)), - intLit(3, p(1, 12)))), - token.Assign, - p(1, 3))) - }) - - expect(t, "a = [1 + 2, b * 4, [4, c]]", func(p pfn) []ast.Stmt { - return stmts( - assignStmt( - exprs(ident("a", p(1, 1))), - exprs(arrayLit(p(1, 5), p(1, 26), - binaryExpr( - intLit(1, p(1, 6)), - intLit(2, p(1, 10)), - token.Add, - p(1, 8)), - binaryExpr( - ident("b", p(1, 13)), - intLit(4, p(1, 17)), - token.Mul, - p(1, 15)), - arrayLit(p(1, 20), p(1, 25), - intLit(4, p(1, 21)), - ident("c", p(1, 24))))), - token.Assign, - p(1, 3))) - }) - - expectError(t, `[1, 2, 3,]`) - expectError(t, ` -[ - 1, - 2, - 3, -]`) - expectError(t, ` -[ - 1, - 2, - 3, - -]`) - expectError(t, `[1, 2, 3, ,]`) -} diff --git a/compiler/parser/parser_assignment_test.go b/compiler/parser/parser_assignment_test.go deleted file mode 100644 index 0abf122..0000000 --- a/compiler/parser/parser_assignment_test.go +++ /dev/null @@ -1,132 +0,0 @@ -package parser_test - -import ( - "testing" - - "github.com/d5/tengo/compiler/ast" - "github.com/d5/tengo/compiler/token" -) - -func TestAssignment(t *testing.T) { - expect(t, "a = 5", func(p pfn) []ast.Stmt { - return stmts( - assignStmt( - exprs(ident("a", p(1, 1))), - exprs(intLit(5, p(1, 5))), - token.Assign, - p(1, 3))) - }) - - expect(t, "a := 5", func(p pfn) []ast.Stmt { - return stmts( - assignStmt( - exprs(ident("a", p(1, 1))), - exprs(intLit(5, p(1, 6))), - token.Define, - p(1, 3))) - }) - - expect(t, "a, b = 5, 10", func(p pfn) []ast.Stmt { - return stmts( - assignStmt( - exprs( - ident("a", p(1, 1)), - ident("b", p(1, 4))), - exprs( - intLit(5, p(1, 8)), - intLit(10, p(1, 11))), - token.Assign, - p(1, 6))) - }) - - expect(t, "a, b := 5, 10", func(p pfn) []ast.Stmt { - return stmts( - assignStmt( - exprs( - ident("a", p(1, 1)), - ident("b", p(1, 4))), - exprs( - intLit(5, p(1, 9)), - intLit(10, p(1, 12))), - token.Define, - p(1, 6))) - }) - - expect(t, "a, b = a + 2, b - 8", func(p pfn) []ast.Stmt { - return stmts( - assignStmt( - exprs( - ident("a", p(1, 1)), - ident("b", p(1, 4))), - exprs( - binaryExpr( - ident("a", p(1, 8)), - intLit(2, p(1, 12)), - token.Add, - p(1, 10)), - binaryExpr( - ident("b", p(1, 15)), - intLit(8, p(1, 19)), - token.Sub, - p(1, 17))), - token.Assign, - p(1, 6))) - }) - - expect(t, "a = [1, 2, 3]", func(p pfn) []ast.Stmt { - return stmts( - assignStmt( - exprs(ident("a", p(1, 1))), - exprs(arrayLit(p(1, 5), p(1, 13), - intLit(1, p(1, 6)), - intLit(2, p(1, 9)), - intLit(3, p(1, 12)))), - token.Assign, - p(1, 3))) - }) - - expect(t, "a = [1 + 2, b * 4, [4, c]]", func(p pfn) []ast.Stmt { - return stmts( - assignStmt( - exprs(ident("a", p(1, 1))), - exprs(arrayLit(p(1, 5), p(1, 26), - binaryExpr( - intLit(1, p(1, 6)), - intLit(2, p(1, 10)), - token.Add, - p(1, 8)), - binaryExpr( - ident("b", p(1, 13)), - intLit(4, p(1, 17)), - token.Mul, - p(1, 15)), - arrayLit(p(1, 20), p(1, 25), - intLit(4, p(1, 21)), - ident("c", p(1, 24))))), - token.Assign, - p(1, 3))) - }) - - expect(t, "a += 5", func(p pfn) []ast.Stmt { - return stmts( - assignStmt( - exprs(ident("a", p(1, 1))), - exprs(intLit(5, p(1, 6))), - token.AddAssign, - p(1, 3))) - }) - - expect(t, "a *= 5 + 10", func(p pfn) []ast.Stmt { - return stmts( - assignStmt( - exprs(ident("a", p(1, 1))), - exprs( - binaryExpr( - intLit(5, p(1, 6)), - intLit(10, p(1, 10)), - token.Add, - p(1, 8))), - token.MulAssign, - p(1, 3))) - }) -} diff --git a/compiler/parser/parser_boolean_test.go b/compiler/parser/parser_boolean_test.go deleted file mode 100644 index 5624236..0000000 --- a/compiler/parser/parser_boolean_test.go +++ /dev/null @@ -1,41 +0,0 @@ -package parser_test - -import ( - "testing" - - "github.com/d5/tengo/compiler/ast" - "github.com/d5/tengo/compiler/token" -) - -func TestBoolean(t *testing.T) { - expect(t, "true", func(p pfn) []ast.Stmt { - return stmts( - exprStmt( - boolLit(true, p(1, 1)))) - }) - - expect(t, "false", func(p pfn) []ast.Stmt { - return stmts( - exprStmt( - boolLit(false, p(1, 1)))) - }) - - expect(t, "true != false", func(p pfn) []ast.Stmt { - return stmts( - exprStmt( - binaryExpr( - boolLit(true, p(1, 1)), - boolLit(false, p(1, 9)), - token.NotEqual, - p(1, 6)))) - }) - - expect(t, "!false", func(p pfn) []ast.Stmt { - return stmts( - exprStmt( - unaryExpr( - boolLit(false, p(1, 2)), - token.Not, - p(1, 1)))) - }) -} diff --git a/compiler/parser/parser_call_test.go b/compiler/parser/parser_call_test.go deleted file mode 100644 index 2424707..0000000 --- a/compiler/parser/parser_call_test.go +++ /dev/null @@ -1,144 +0,0 @@ -package parser_test - -import ( - "testing" - - "github.com/d5/tengo/compiler/ast" - "github.com/d5/tengo/compiler/token" -) - -func TestCall(t *testing.T) { - expect(t, "add(1, 2, 3)", func(p pfn) []ast.Stmt { - return stmts( - exprStmt( - callExpr( - ident("add", p(1, 1)), - p(1, 4), p(1, 12), - intLit(1, p(1, 5)), - intLit(2, p(1, 8)), - intLit(3, p(1, 11))))) - }) - - expect(t, "a = add(1, 2, 3)", func(p pfn) []ast.Stmt { - return stmts( - assignStmt( - exprs( - ident("a", p(1, 1))), - exprs( - callExpr( - ident("add", p(1, 5)), - p(1, 8), p(1, 16), - intLit(1, p(1, 9)), - intLit(2, p(1, 12)), - intLit(3, p(1, 15)))), - token.Assign, - p(1, 3))) - }) - - expect(t, "a, b = add(1, 2, 3)", func(p pfn) []ast.Stmt { - return stmts( - assignStmt( - exprs( - ident("a", p(1, 1)), - ident("b", p(1, 4))), - exprs( - callExpr( - ident("add", p(1, 8)), - p(1, 11), p(1, 19), - intLit(1, p(1, 12)), - intLit(2, p(1, 15)), - intLit(3, p(1, 18)))), - token.Assign, - p(1, 6))) - }) - - expect(t, "add(a + 1, 2 * 1, (b + c))", func(p pfn) []ast.Stmt { - return stmts( - exprStmt( - callExpr( - ident("add", p(1, 1)), - p(1, 4), p(1, 26), - binaryExpr( - ident("a", p(1, 5)), - intLit(1, p(1, 9)), - token.Add, - p(1, 7)), - binaryExpr( - intLit(2, p(1, 12)), - intLit(1, p(1, 16)), - token.Mul, - p(1, 14)), - parenExpr( - binaryExpr( - ident("b", p(1, 20)), - ident("c", p(1, 24)), - token.Add, - p(1, 22)), - p(1, 19), p(1, 25))))) - }) - - expectString(t, "a + add(b * c) + d", "((a + add((b * c))) + d)") - expectString(t, "add(a, b, 1, 2 * 3, 4 + 5, add(6, 7 * 8))", "add(a, b, 1, (2 * 3), (4 + 5), add(6, (7 * 8)))") - expectString(t, "f1(a) + f2(b) * f3(c)", "(f1(a) + (f2(b) * f3(c)))") - expectString(t, "(f1(a) + f2(b)) * f3(c)", "(((f1(a) + f2(b))) * f3(c))") - - expect(t, "func(a, b) { a + b }(1, 2)", func(p pfn) []ast.Stmt { - return stmts( - exprStmt( - callExpr( - funcLit( - funcType( - identList( - p(1, 5), p(1, 10), - false, - ident("a", p(1, 6)), - ident("b", p(1, 9))), - p(1, 1)), - blockStmt( - p(1, 12), p(1, 20), - exprStmt( - binaryExpr( - ident("a", p(1, 14)), - ident("b", p(1, 18)), - token.Add, - p(1, 16))))), - p(1, 21), p(1, 26), - intLit(1, p(1, 22)), - intLit(2, p(1, 25))))) - }) - - expect(t, `a.b()`, func(p pfn) []ast.Stmt { - return stmts( - exprStmt( - callExpr( - selectorExpr( - ident("a", p(1, 1)), - stringLit("b", p(1, 3))), - p(1, 4), p(1, 5)))) - }) - - expect(t, `a.b.c()`, func(p pfn) []ast.Stmt { - return stmts( - exprStmt( - callExpr( - selectorExpr( - selectorExpr( - ident("a", p(1, 1)), - stringLit("b", p(1, 3))), - stringLit("c", p(1, 5))), - p(1, 6), p(1, 7)))) - }) - - expect(t, `a["b"].c()`, func(p pfn) []ast.Stmt { - return stmts( - exprStmt( - callExpr( - selectorExpr( - indexExpr( - ident("a", p(1, 1)), - stringLit("b", p(1, 3)), - p(1, 2), p(1, 6)), - stringLit("c", p(1, 8))), - p(1, 9), p(1, 10)))) - }) -} diff --git a/compiler/parser/parser_char_test.go b/compiler/parser/parser_char_test.go deleted file mode 100644 index a36c8ba..0000000 --- a/compiler/parser/parser_char_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package parser_test - -import ( - "testing" - - "github.com/d5/tengo/compiler/ast" -) - -func TestChar(t *testing.T) { - expect(t, `'A'`, func(p pfn) []ast.Stmt { - return stmts( - exprStmt( - charLit('A', 1))) - }) - expect(t, `'九'`, func(p pfn) []ast.Stmt { - return stmts( - exprStmt( - charLit('九', 1))) - }) - - expectError(t, `''`) - expectError(t, `'AB'`) - expectError(t, `'A九'`) -} diff --git a/compiler/parser/parser_cond_test.go b/compiler/parser/parser_cond_test.go deleted file mode 100644 index ae8684b..0000000 --- a/compiler/parser/parser_cond_test.go +++ /dev/null @@ -1,48 +0,0 @@ -package parser_test - -import ( - "testing" - - "github.com/d5/tengo/compiler/ast" -) - -func TestCondExpr(t *testing.T) { - expect(t, "a ? b : c", func(p pfn) []ast.Stmt { - return stmts( - exprStmt( - condExpr( - ident("a", p(1, 1)), - ident("b", p(1, 5)), - ident("c", p(1, 9)), - p(1, 3), - p(1, 7)))) - }) - expect(t, `a ? -b : -c`, func(p pfn) []ast.Stmt { - return stmts( - exprStmt( - condExpr( - ident("a", p(1, 1)), - ident("b", p(1, 5)), - ident("c", p(1, 9)), - p(1, 3), - p(1, 7)))) - }) - - expectString(t, `a ? b : c`, "(a ? b : c)") - expectString(t, `a + b ? c - d : e * f`, "((a + b) ? (c - d) : (e * f))") - expectString(t, `a == b ? c + (d / e) : f ? g : h + i`, "((a == b) ? (c + ((d / e))) : (f ? g : (h + i)))") - expectString(t, `(a + b) ? (c - d) : (e * f)`, "(((a + b)) ? ((c - d)) : ((e * f)))") - expectString(t, `a + (b ? c : d) - e`, "((a + ((b ? c : d))) - e)") - expectString(t, `a ? b ? c : d : e`, "(a ? (b ? c : d) : e)") - expectString(t, `a := b ? c : d`, "a := (b ? c : d)") - expectString(t, `x := a ? b ? c : d : e`, "x := (a ? (b ? c : d) : e)") - - // ? : should be at the end of each line if it's multi-line - expectError(t, `a -? b -: c`) - expectError(t, `a ? (b : e)`) - expectError(t, `(a ? b) : e`) -} diff --git a/compiler/parser/parser_error_test.go b/compiler/parser/parser_error_test.go deleted file mode 100644 index 0375e14..0000000 --- a/compiler/parser/parser_error_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package parser_test - -import ( - "testing" - - "github.com/d5/tengo/compiler/ast" - "github.com/d5/tengo/compiler/token" -) - -func TestError(t *testing.T) { - expect(t, `error(1234)`, func(p pfn) []ast.Stmt { - return stmts( - exprStmt( - errorExpr(p(1, 1), intLit(1234, p(1, 7)), p(1, 6), p(1, 11)))) - }) - - expect(t, `err1 := error("some error")`, func(p pfn) []ast.Stmt { - return stmts( - assignStmt( - exprs(ident("err1", p(1, 1))), - exprs(errorExpr(p(1, 9), stringLit("some error", p(1, 15)), p(1, 14), p(1, 27))), - token.Define, p(1, 6))) - }) - - expect(t, `return error("some error")`, func(p pfn) []ast.Stmt { - return stmts( - returnStmt(p(1, 1), - errorExpr(p(1, 8), stringLit("some error", p(1, 14)), p(1, 13), p(1, 26)))) - }) - - expect(t, `return error("some" + "error")`, func(p pfn) []ast.Stmt { - return stmts( - returnStmt(p(1, 1), - errorExpr(p(1, 8), - binaryExpr( - stringLit("some", p(1, 14)), - stringLit("error", p(1, 23)), - token.Add, p(1, 21)), - p(1, 13), p(1, 30)))) - }) - - expectError(t, `error()`) // must have a value -} diff --git a/compiler/parser/parser_for_in_test.go b/compiler/parser/parser_for_in_test.go deleted file mode 100644 index 9f40cb8..0000000 --- a/compiler/parser/parser_for_in_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package parser_test - -import ( - "testing" - - "github.com/d5/tengo/compiler/ast" -) - -func TestForIn(t *testing.T) { - expect(t, "for x in y {}", func(p pfn) []ast.Stmt { - return stmts( - forInStmt( - ident("_", p(1, 5)), - ident("x", p(1, 5)), - ident("y", p(1, 10)), - blockStmt(p(1, 12), p(1, 13)), - p(1, 1))) - }) - - expect(t, "for _ in y {}", func(p pfn) []ast.Stmt { - return stmts( - forInStmt( - ident("_", p(1, 5)), - ident("_", p(1, 5)), - ident("y", p(1, 10)), - blockStmt(p(1, 12), p(1, 13)), - p(1, 1))) - }) - - expect(t, "for x in [1, 2, 3] {}", func(p pfn) []ast.Stmt { - return stmts( - forInStmt( - ident("_", p(1, 5)), - ident("x", p(1, 5)), - arrayLit( - p(1, 10), p(1, 18), - intLit(1, p(1, 11)), - intLit(2, p(1, 14)), - intLit(3, p(1, 17))), - blockStmt(p(1, 20), p(1, 21)), - p(1, 1))) - }) - - expect(t, "for x, y in z {}", func(p pfn) []ast.Stmt { - return stmts( - forInStmt( - ident("x", p(1, 5)), - ident("y", p(1, 8)), - ident("z", p(1, 13)), - blockStmt(p(1, 15), p(1, 16)), - p(1, 1))) - }) - - expect(t, "for x, y in {k1: 1, k2: 2} {}", func(p pfn) []ast.Stmt { - return stmts( - forInStmt( - ident("x", p(1, 5)), - ident("y", p(1, 8)), - mapLit( - p(1, 13), p(1, 26), - mapElementLit("k1", p(1, 14), p(1, 16), intLit(1, p(1, 18))), - mapElementLit("k2", p(1, 21), p(1, 23), intLit(2, p(1, 25)))), - blockStmt(p(1, 28), p(1, 29)), - p(1, 1))) - }) -} diff --git a/compiler/parser/parser_for_test.go b/compiler/parser/parser_for_test.go deleted file mode 100644 index 52cffd4..0000000 --- a/compiler/parser/parser_for_test.go +++ /dev/null @@ -1,118 +0,0 @@ -package parser_test - -import ( - "testing" - - "github.com/d5/tengo/compiler/ast" - "github.com/d5/tengo/compiler/token" -) - -func TestFor(t *testing.T) { - expect(t, "for {}", func(p pfn) []ast.Stmt { - return stmts( - forStmt(nil, nil, nil, blockStmt(p(1, 5), p(1, 6)), p(1, 1))) - }) - - expect(t, "for a == 5 {}", func(p pfn) []ast.Stmt { - return stmts( - forStmt( - nil, - binaryExpr( - ident("a", p(1, 5)), - intLit(5, p(1, 10)), - token.Equal, - p(1, 7)), - nil, - blockStmt(p(1, 12), p(1, 13)), - p(1, 1))) - }) - - expect(t, "for a := 0; a == 5; {}", func(p pfn) []ast.Stmt { - return stmts( - forStmt( - assignStmt( - exprs(ident("a", p(1, 5))), - exprs(intLit(0, p(1, 10))), - token.Define, p(1, 7)), - binaryExpr( - ident("a", p(1, 13)), - intLit(5, p(1, 18)), - token.Equal, - p(1, 15)), - nil, - blockStmt(p(1, 22), p(1, 23)), - p(1, 1))) - }) - - expect(t, "for a := 0; a < 5; a++ {}", func(p pfn) []ast.Stmt { - return stmts( - forStmt( - assignStmt( - exprs(ident("a", p(1, 5))), - exprs(intLit(0, p(1, 10))), - token.Define, p(1, 7)), - binaryExpr( - ident("a", p(1, 13)), - intLit(5, p(1, 17)), - token.Less, - p(1, 15)), - incDecStmt( - ident("a", p(1, 20)), - token.Inc, p(1, 21)), - blockStmt(p(1, 24), p(1, 25)), - p(1, 1))) - }) - - expect(t, "for ; a < 5; a++ {}", func(p pfn) []ast.Stmt { - return stmts( - forStmt( - nil, - binaryExpr( - ident("a", p(1, 7)), - intLit(5, p(1, 11)), - token.Less, - p(1, 9)), - incDecStmt( - ident("a", p(1, 14)), - token.Inc, p(1, 15)), - blockStmt(p(1, 18), p(1, 19)), - p(1, 1))) - }) - - expect(t, "for a := 0; ; a++ {}", func(p pfn) []ast.Stmt { - return stmts( - forStmt( - assignStmt( - exprs(ident("a", p(1, 5))), - exprs(intLit(0, p(1, 10))), - token.Define, p(1, 7)), - nil, - incDecStmt( - ident("a", p(1, 15)), - token.Inc, p(1, 16)), - blockStmt(p(1, 19), p(1, 20)), - p(1, 1))) - }) - - expect(t, "for a == 5 && b != 4 {}", func(p pfn) []ast.Stmt { - return stmts( - forStmt( - nil, - binaryExpr( - binaryExpr( - ident("a", p(1, 5)), - intLit(5, p(1, 10)), - token.Equal, - p(1, 7)), - binaryExpr( - ident("b", p(1, 15)), - intLit(4, p(1, 20)), - token.NotEqual, - p(1, 17)), - token.LAnd, - p(1, 12)), - nil, - blockStmt(p(1, 22), p(1, 23)), - p(1, 1))) - }) -} diff --git a/compiler/parser/parser_function_test.go b/compiler/parser/parser_function_test.go deleted file mode 100644 index fc1555a..0000000 --- a/compiler/parser/parser_function_test.go +++ /dev/null @@ -1,86 +0,0 @@ -package parser_test - -import ( - "testing" - - "github.com/d5/tengo/compiler/ast" - "github.com/d5/tengo/compiler/token" -) - -func TestFunction(t *testing.T) { - expect(t, "a = func(b, c, d) { return d }", func(p pfn) []ast.Stmt { - return stmts( - assignStmt( - exprs( - ident("a", p(1, 1))), - exprs( - funcLit( - funcType( - identList(p(1, 9), p(1, 17), false, - ident("b", p(1, 10)), - ident("c", p(1, 13)), - ident("d", p(1, 16))), - p(1, 5)), - blockStmt(p(1, 19), p(1, 30), - returnStmt(p(1, 21), ident("d", p(1, 28)))))), - token.Assign, - p(1, 3))) - }) -} - -func TestVariableFunction(t *testing.T) { - expect(t, "a = func(...args) { return args }", func(p pfn) []ast.Stmt { - return stmts( - assignStmt( - exprs( - ident("a", p(1, 1))), - exprs( - funcLit( - funcType( - identList( - p(1, 9), p(1, 17), - true, - ident("args", p(1, 13)), - ), p(1, 5)), - blockStmt(p(1, 19), p(1, 33), - returnStmt(p(1, 21), - ident("args", p(1, 28)), - ), - ), - ), - ), - token.Assign, - p(1, 3))) - }) -} - -func TestVariableFunctionWithArgs(t *testing.T) { - expect(t, "a = func(x, y, ...z) { return z }", func(p pfn) []ast.Stmt { - return stmts( - assignStmt( - exprs( - ident("a", p(1, 1))), - exprs( - funcLit( - funcType( - identList( - p(1, 9), p(1, 20), - true, - ident("x", p(1, 10)), - ident("y", p(1, 13)), - ident("z", p(1, 19)), - ), p(1, 5)), - blockStmt(p(1, 22), p(1, 33), - returnStmt(p(1, 24), - ident("z", p(1, 31)), - ), - ), - ), - ), - token.Assign, - p(1, 3))) - }) - - expectError(t, "a = func(x, y, ...z, invalid) { return z }") - expectError(t, "a = func(...args, invalid) { return args }") -} diff --git a/compiler/parser/parser_if_test.go b/compiler/parser/parser_if_test.go deleted file mode 100644 index db4fe85..0000000 --- a/compiler/parser/parser_if_test.go +++ /dev/null @@ -1,215 +0,0 @@ -package parser_test - -import ( - "testing" - - "github.com/d5/tengo/compiler/ast" - "github.com/d5/tengo/compiler/token" -) - -func TestIf(t *testing.T) { - expect(t, "if a == 5 {}", func(p pfn) []ast.Stmt { - return stmts( - ifStmt( - nil, - binaryExpr( - ident("a", p(1, 4)), - intLit(5, p(1, 9)), - token.Equal, - p(1, 6)), - blockStmt( - p(1, 11), p(1, 12)), - nil, - p(1, 1))) - }) - - expect(t, "if a == 5 && b != 3 {}", func(p pfn) []ast.Stmt { - return stmts( - ifStmt( - nil, - binaryExpr( - binaryExpr( - ident("a", p(1, 4)), - intLit(5, p(1, 9)), - token.Equal, - p(1, 6)), - binaryExpr( - ident("b", p(1, 14)), - intLit(3, p(1, 19)), - token.NotEqual, - p(1, 16)), - token.LAnd, - p(1, 11)), - blockStmt( - p(1, 21), p(1, 22)), - nil, - p(1, 1))) - }) - - expect(t, "if a == 5 { a = 3; a = 1 }", func(p pfn) []ast.Stmt { - return stmts( - ifStmt( - nil, - binaryExpr( - ident("a", p(1, 4)), - intLit(5, p(1, 9)), - token.Equal, - p(1, 6)), - blockStmt( - p(1, 11), p(1, 26), - assignStmt( - exprs(ident("a", p(1, 13))), - exprs(intLit(3, p(1, 17))), - token.Assign, - p(1, 15)), - assignStmt( - exprs(ident("a", p(1, 20))), - exprs(intLit(1, p(1, 24))), - token.Assign, - p(1, 22))), - nil, - p(1, 1))) - }) - - expect(t, "if a == 5 { a = 3; a = 1 } else { a = 2; a = 4 }", func(p pfn) []ast.Stmt { - return stmts( - ifStmt( - nil, - binaryExpr( - ident("a", p(1, 4)), - intLit(5, p(1, 9)), - token.Equal, - p(1, 6)), - blockStmt( - p(1, 11), p(1, 26), - assignStmt( - exprs(ident("a", p(1, 13))), - exprs(intLit(3, p(1, 17))), - token.Assign, - p(1, 15)), - assignStmt( - exprs(ident("a", p(1, 20))), - exprs(intLit(1, p(1, 24))), - token.Assign, - p(1, 22))), - blockStmt( - p(1, 33), p(1, 48), - assignStmt( - exprs(ident("a", p(1, 35))), - exprs(intLit(2, p(1, 39))), - token.Assign, - p(1, 37)), - assignStmt( - exprs(ident("a", p(1, 42))), - exprs(intLit(4, p(1, 46))), - token.Assign, - p(1, 44))), - p(1, 1))) - }) - - expect(t, ` -if a == 5 { - b = 3 - c = 1 -} else if d == 3 { - e = 8 - f = 3 -} else { - g = 2 - h = 4 -}`, func(p pfn) []ast.Stmt { - return stmts( - ifStmt( - nil, - binaryExpr( - ident("a", p(2, 4)), - intLit(5, p(2, 9)), - token.Equal, - p(2, 6)), - blockStmt( - p(2, 11), p(5, 1), - assignStmt( - exprs(ident("b", p(3, 2))), - exprs(intLit(3, p(3, 6))), - token.Assign, - p(3, 4)), - assignStmt( - exprs(ident("c", p(4, 2))), - exprs(intLit(1, p(4, 6))), - token.Assign, - p(4, 4))), - ifStmt( - nil, - binaryExpr( - ident("d", p(5, 11)), - intLit(3, p(5, 16)), - token.Equal, - p(5, 13)), - blockStmt( - p(5, 18), p(8, 1), - assignStmt( - exprs(ident("e", p(6, 2))), - exprs(intLit(8, p(6, 6))), - token.Assign, - p(6, 4)), - assignStmt( - exprs(ident("f", p(7, 2))), - exprs(intLit(3, p(7, 6))), - token.Assign, - p(7, 4))), - blockStmt( - p(8, 8), p(11, 1), - assignStmt( - exprs(ident("g", p(9, 2))), - exprs(intLit(2, p(9, 6))), - token.Assign, - p(9, 4)), - assignStmt( - exprs(ident("h", p(10, 2))), - exprs(intLit(4, p(10, 6))), - token.Assign, - p(10, 4))), - p(5, 8)), - p(2, 1))) - }) - - expect(t, "if a := 3; a < b {}", func(p pfn) []ast.Stmt { - return stmts( - ifStmt( - assignStmt( - exprs(ident("a", p(1, 4))), - exprs(intLit(3, p(1, 9))), - token.Define, p(1, 6)), - binaryExpr( - ident("a", p(1, 12)), - ident("b", p(1, 16)), - token.Less, p(1, 14)), - blockStmt( - p(1, 18), p(1, 19)), - nil, - p(1, 1))) - }) - - expect(t, "if a++; a < b {}", func(p pfn) []ast.Stmt { - return stmts( - ifStmt( - incDecStmt(ident("a", p(1, 4)), token.Inc, p(1, 5)), - binaryExpr( - ident("a", p(1, 9)), - ident("b", p(1, 13)), - token.Less, p(1, 11)), - blockStmt( - p(1, 15), p(1, 16)), - nil, - p(1, 1))) - }) - - expectError(t, `if {}`) - expectError(t, `if a == b { } else a != b { }`) - expectError(t, `if a == b { } else if { }`) - expectError(t, `else { }`) - expectError(t, `if ; {}`) - expectError(t, `if a := 3; {}`) - expectError(t, `if ; a < 3 {}`) - -} diff --git a/compiler/parser/parser_import_test.go b/compiler/parser/parser_import_test.go deleted file mode 100644 index c263350..0000000 --- a/compiler/parser/parser_import_test.go +++ /dev/null @@ -1,46 +0,0 @@ -package parser_test - -import ( - "testing" - - "github.com/d5/tengo/compiler/ast" - "github.com/d5/tengo/compiler/token" -) - -func TestImport(t *testing.T) { - expect(t, `a := import("mod1")`, func(p pfn) []ast.Stmt { - return stmts( - assignStmt( - exprs(ident("a", p(1, 1))), - exprs(importExpr("mod1", p(1, 6))), - token.Define, p(1, 3))) - }) - - expect(t, `import("mod1").var1`, func(p pfn) []ast.Stmt { - return stmts( - exprStmt( - selectorExpr( - importExpr("mod1", p(1, 1)), - stringLit("var1", p(1, 16))))) - }) - - expect(t, `import("mod1").func1()`, func(p pfn) []ast.Stmt { - return stmts( - exprStmt( - callExpr( - selectorExpr( - importExpr("mod1", p(1, 1)), - stringLit("func1", p(1, 16))), - p(1, 21), p(1, 22)))) - }) - - expect(t, `for x, y in import("mod1") {}`, func(p pfn) []ast.Stmt { - return stmts( - forInStmt( - ident("x", p(1, 5)), - ident("y", p(1, 8)), - importExpr("mod1", p(1, 13)), - blockStmt(p(1, 28), p(1, 29)), - p(1, 1))) - }) -} diff --git a/compiler/parser/parser_index_test.go b/compiler/parser/parser_index_test.go deleted file mode 100644 index 176c023..0000000 --- a/compiler/parser/parser_index_test.go +++ /dev/null @@ -1,98 +0,0 @@ -package parser_test - -import ( - "testing" - - "github.com/d5/tengo/compiler/ast" - "github.com/d5/tengo/compiler/token" -) - -func TestIndex(t *testing.T) { - expect(t, "[1, 2, 3][1]", func(p pfn) []ast.Stmt { - return stmts( - exprStmt( - indexExpr( - arrayLit(p(1, 1), p(1, 9), - intLit(1, p(1, 2)), - intLit(2, p(1, 5)), - intLit(3, p(1, 8))), - intLit(1, p(1, 11)), - p(1, 10), p(1, 12)))) - }) - - expect(t, "[1, 2, 3][5 - a]", func(p pfn) []ast.Stmt { - return stmts( - exprStmt( - indexExpr( - arrayLit(p(1, 1), p(1, 9), - intLit(1, p(1, 2)), - intLit(2, p(1, 5)), - intLit(3, p(1, 8))), - binaryExpr( - intLit(5, p(1, 11)), - ident("a", p(1, 15)), - token.Sub, - p(1, 13)), - p(1, 10), p(1, 16)))) - }) - - expect(t, "[1, 2, 3][5 : a]", func(p pfn) []ast.Stmt { - return stmts( - exprStmt( - sliceExpr( - arrayLit(p(1, 1), p(1, 9), - intLit(1, p(1, 2)), - intLit(2, p(1, 5)), - intLit(3, p(1, 8))), - intLit(5, p(1, 11)), - ident("a", p(1, 15)), - p(1, 10), p(1, 16)))) - }) - - expect(t, "[1, 2, 3][a + 3 : b - 8]", func(p pfn) []ast.Stmt { - return stmts( - exprStmt( - sliceExpr( - arrayLit(p(1, 1), p(1, 9), - intLit(1, p(1, 2)), - intLit(2, p(1, 5)), - intLit(3, p(1, 8))), - binaryExpr( - ident("a", p(1, 11)), - intLit(3, p(1, 15)), - token.Add, - p(1, 13)), - binaryExpr( - ident("b", p(1, 19)), - intLit(8, p(1, 23)), - token.Sub, - p(1, 21)), - p(1, 10), p(1, 24)))) - }) - - expect(t, `{a: 1, b: 2}["b"]`, func(p pfn) []ast.Stmt { - return stmts( - exprStmt( - indexExpr( - mapLit(p(1, 1), p(1, 12), - mapElementLit("a", p(1, 2), p(1, 3), intLit(1, p(1, 5))), - mapElementLit("b", p(1, 8), p(1, 9), intLit(2, p(1, 11)))), - stringLit("b", p(1, 14)), - p(1, 13), p(1, 17)))) - }) - - expect(t, `{a: 1, b: 2}[a + b]`, func(p pfn) []ast.Stmt { - return stmts( - exprStmt( - indexExpr( - mapLit(p(1, 1), p(1, 12), - mapElementLit("a", p(1, 2), p(1, 3), intLit(1, p(1, 5))), - mapElementLit("b", p(1, 8), p(1, 9), intLit(2, p(1, 11)))), - binaryExpr( - ident("a", p(1, 14)), - ident("b", p(1, 18)), - token.Add, - p(1, 16)), - p(1, 13), p(1, 19)))) - }) -} diff --git a/compiler/parser/parser_logical_test.go b/compiler/parser/parser_logical_test.go deleted file mode 100644 index 2a2bdd4..0000000 --- a/compiler/parser/parser_logical_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package parser_test - -import ( - "testing" - - "github.com/d5/tengo/compiler/ast" - "github.com/d5/tengo/compiler/token" -) - -func TestLogical(t *testing.T) { - expect(t, "a && 5 || true", func(p pfn) []ast.Stmt { - return stmts( - exprStmt( - binaryExpr( - binaryExpr( - ident("a", p(1, 1)), - intLit(5, p(1, 6)), - token.LAnd, - p(1, 3)), - boolLit(true, p(1, 11)), - token.LOr, - p(1, 8)))) - }) - - expect(t, "a || 5 && true", func(p pfn) []ast.Stmt { - return stmts( - exprStmt( - binaryExpr( - ident("a", p(1, 1)), - binaryExpr( - intLit(5, p(1, 6)), - boolLit(true, p(1, 11)), - token.LAnd, - p(1, 8)), - token.LOr, - p(1, 3)))) - }) - - expect(t, "a && (5 || true)", func(p pfn) []ast.Stmt { - return stmts( - exprStmt( - binaryExpr( - ident("a", p(1, 1)), - parenExpr( - binaryExpr( - intLit(5, p(1, 7)), - boolLit(true, p(1, 12)), - token.LOr, - p(1, 9)), - p(1, 6), p(1, 16)), - token.LAnd, - p(1, 3)))) - }) -} diff --git a/compiler/parser/parser_map_test.go b/compiler/parser/parser_map_test.go deleted file mode 100644 index 147ccb0..0000000 --- a/compiler/parser/parser_map_test.go +++ /dev/null @@ -1,80 +0,0 @@ -package parser_test - -import ( - "testing" - - "github.com/d5/tengo/compiler/ast" - "github.com/d5/tengo/compiler/token" -) - -func TestMap(t *testing.T) { - expect(t, "{ key1: 1, key2: \"2\", key3: true }", func(p pfn) []ast.Stmt { - return stmts( - exprStmt( - mapLit(p(1, 1), p(1, 34), - mapElementLit("key1", p(1, 3), p(1, 7), intLit(1, p(1, 9))), - mapElementLit("key2", p(1, 12), p(1, 16), stringLit("2", p(1, 18))), - mapElementLit("key3", p(1, 23), p(1, 27), boolLit(true, p(1, 29)))))) - }) - - expect(t, "{ \"key1\": 1 }", func(p pfn) []ast.Stmt { - return stmts( - exprStmt( - mapLit(p(1, 1), p(1, 13), - mapElementLit("key1", p(1, 3), p(1, 9), intLit(1, p(1, 11)))))) - }) - - expect(t, "a = { key1: 1, key2: \"2\", key3: true }", func(p pfn) []ast.Stmt { - return stmts( - assignStmt( - exprs(ident("a", p(1, 1))), - exprs(mapLit(p(1, 5), p(1, 38), - mapElementLit("key1", p(1, 7), p(1, 11), intLit(1, p(1, 13))), - mapElementLit("key2", p(1, 16), p(1, 20), stringLit("2", p(1, 22))), - mapElementLit("key3", p(1, 27), p(1, 31), boolLit(true, p(1, 33))))), - token.Assign, - p(1, 3))) - }) - - expect(t, "a = { key1: 1, key2: \"2\", key3: { k1: `bar`, k2: 4 } }", func(p pfn) []ast.Stmt { - return stmts( - assignStmt( - exprs(ident("a", p(1, 1))), - exprs(mapLit(p(1, 5), p(1, 54), - mapElementLit("key1", p(1, 7), p(1, 11), intLit(1, p(1, 13))), - mapElementLit("key2", p(1, 16), p(1, 20), stringLit("2", p(1, 22))), - mapElementLit("key3", p(1, 27), p(1, 31), - mapLit(p(1, 33), p(1, 52), - mapElementLit("k1", p(1, 35), p(1, 37), stringLit("bar", p(1, 39))), - mapElementLit("k2", p(1, 46), p(1, 48), intLit(4, p(1, 50))))))), - token.Assign, - p(1, 3))) - }) - - expect(t, ` -{ - key1: 1, - key2: "2", - key3: true -}`, func(p pfn) []ast.Stmt { - return stmts( - exprStmt( - mapLit(p(2, 1), p(6, 1), - mapElementLit("key1", p(3, 2), p(3, 6), intLit(1, p(3, 8))), - mapElementLit("key2", p(4, 2), p(4, 6), stringLit("2", p(4, 8))), - mapElementLit("key3", p(5, 2), p(5, 6), boolLit(true, p(5, 8)))))) - }) - - expectError(t, ` -{ - key1: 1, - key2: "2", - key3: true, -}`) // unlike Go, trailing comma for the last element is illegal - - expectError(t, `{ key1: 1, }`) - expectError(t, `{ -key1: 1, -key2: 2, -}`) -} diff --git a/compiler/parser/parser_precendence_test.go b/compiler/parser/parser_precendence_test.go deleted file mode 100644 index c8dd998..0000000 --- a/compiler/parser/parser_precendence_test.go +++ /dev/null @@ -1,11 +0,0 @@ -package parser_test - -import ( - "testing" -) - -func TestPrecedence(t *testing.T) { - expectString(t, `a + b + c`, `((a + b) + c)`) - expectString(t, `a + b * c`, `(a + (b * c))`) - expectString(t, `x = 2 * 1 + 3 / 4`, `x = ((2 * 1) + (3 / 4))`) -} diff --git a/compiler/parser/parser_selector_test.go b/compiler/parser/parser_selector_test.go deleted file mode 100644 index be6f62f..0000000 --- a/compiler/parser/parser_selector_test.go +++ /dev/null @@ -1,165 +0,0 @@ -package parser_test - -import ( - "testing" - - "github.com/d5/tengo/compiler/ast" - "github.com/d5/tengo/compiler/token" -) - -func TestSelector(t *testing.T) { - expect(t, "a.b", func(p pfn) []ast.Stmt { - return stmts( - exprStmt( - selectorExpr( - ident("a", p(1, 1)), - stringLit("b", p(1, 3))))) - }) - - expect(t, "a.b.c", func(p pfn) []ast.Stmt { - return stmts( - exprStmt( - selectorExpr( - selectorExpr( - ident("a", p(1, 1)), - stringLit("b", p(1, 3))), - stringLit("c", p(1, 5))))) - }) - - expect(t, "{k1:1}.k1", func(p pfn) []ast.Stmt { - return stmts( - exprStmt( - selectorExpr( - mapLit( - p(1, 1), p(1, 6), - mapElementLit("k1", p(1, 2), p(1, 4), intLit(1, p(1, 5)))), - stringLit("k1", p(1, 8))))) - - }) - expect(t, "{k1:{v1:1}}.k1.v1", func(p pfn) []ast.Stmt { - return stmts( - exprStmt( - selectorExpr( - selectorExpr( - mapLit( - p(1, 1), p(1, 11), - mapElementLit("k1", p(1, 2), p(1, 4), - mapLit(p(1, 5), p(1, 10), - mapElementLit("v1", p(1, 6), p(1, 8), intLit(1, p(1, 9)))))), - stringLit("k1", p(1, 13))), - stringLit("v1", p(1, 16))))) - }) - - expect(t, "a.b = 4", func(p pfn) []ast.Stmt { - return stmts( - assignStmt( - exprs(selectorExpr(ident("a", p(1, 1)), stringLit("b", p(1, 3)))), - exprs(intLit(4, p(1, 7))), - token.Assign, p(1, 5))) - }) - - expect(t, "a.b.c = 4", func(p pfn) []ast.Stmt { - return stmts( - assignStmt( - exprs(selectorExpr(selectorExpr(ident("a", p(1, 1)), stringLit("b", p(1, 3))), stringLit("c", p(1, 5)))), - exprs(intLit(4, p(1, 9))), - token.Assign, p(1, 7))) - }) - - expect(t, "a.b.c = 4 + 5", func(p pfn) []ast.Stmt { - return stmts( - assignStmt( - exprs(selectorExpr(selectorExpr(ident("a", p(1, 1)), stringLit("b", p(1, 3))), stringLit("c", p(1, 5)))), - exprs(binaryExpr(intLit(4, p(1, 9)), intLit(5, p(1, 13)), token.Add, p(1, 11))), - token.Assign, p(1, 7))) - }) - - expect(t, "a[0].c = 4", func(p pfn) []ast.Stmt { - return stmts( - assignStmt( - exprs( - selectorExpr( - indexExpr( - ident("a", p(1, 1)), - intLit(0, p(1, 3)), - p(1, 2), p(1, 4)), - stringLit("c", p(1, 6)))), - exprs(intLit(4, p(1, 10))), - token.Assign, p(1, 8))) - }) - - expect(t, "a.b[0].c = 4", func(p pfn) []ast.Stmt { - return stmts( - assignStmt( - exprs( - selectorExpr( - indexExpr( - selectorExpr( - ident("a", p(1, 1)), - stringLit("b", p(1, 3))), - intLit(0, p(1, 5)), - p(1, 4), p(1, 6)), - stringLit("c", p(1, 8)))), - exprs(intLit(4, p(1, 12))), - token.Assign, p(1, 10))) - }) - - expect(t, "a.b[0][2].c = 4", func(p pfn) []ast.Stmt { - return stmts( - assignStmt( - exprs( - selectorExpr( - indexExpr( - indexExpr( - selectorExpr( - ident("a", p(1, 1)), - stringLit("b", p(1, 3))), - intLit(0, p(1, 5)), - p(1, 4), p(1, 6)), - intLit(2, p(1, 8)), - p(1, 7), p(1, 9)), - stringLit("c", p(1, 11)))), - exprs(intLit(4, p(1, 15))), - token.Assign, p(1, 13))) - }) - - expect(t, `a.b["key1"][2].c = 4`, func(p pfn) []ast.Stmt { - return stmts( - assignStmt( - exprs( - selectorExpr( - indexExpr( - indexExpr( - selectorExpr( - ident("a", p(1, 1)), - stringLit("b", p(1, 3))), - stringLit("key1", p(1, 5)), - p(1, 4), p(1, 11)), - intLit(2, p(1, 13)), - p(1, 12), p(1, 14)), - stringLit("c", p(1, 16)))), - exprs(intLit(4, p(1, 20))), - token.Assign, p(1, 18))) - }) - - expect(t, "a[0].b[2].c = 4", func(p pfn) []ast.Stmt { - return stmts( - assignStmt( - exprs( - selectorExpr( - indexExpr( - selectorExpr( - indexExpr( - ident("a", p(1, 1)), - intLit(0, p(1, 3)), - p(1, 2), p(1, 4)), - stringLit("b", p(1, 6))), - intLit(2, p(1, 8)), - p(1, 7), p(1, 9)), - stringLit("c", p(1, 11)))), - exprs(intLit(4, p(1, 15))), - token.Assign, p(1, 13))) - }) - - expectError(t, `a.(b.c)`) -} diff --git a/compiler/parser/parser_semicolon_test.go b/compiler/parser/parser_semicolon_test.go deleted file mode 100644 index b69e5a1..0000000 --- a/compiler/parser/parser_semicolon_test.go +++ /dev/null @@ -1,45 +0,0 @@ -package parser_test - -import ( - "testing" - - "github.com/d5/tengo/compiler/ast" -) - -func TestSemicolon(t *testing.T) { - expect(t, "1", func(p pfn) []ast.Stmt { - return stmts( - exprStmt(intLit(1, p(1, 1)))) - }) - - expect(t, "1;", func(p pfn) []ast.Stmt { - return stmts( - exprStmt(intLit(1, p(1, 1)))) - }) - - expect(t, "1;;", func(p pfn) []ast.Stmt { - return stmts( - exprStmt(intLit(1, p(1, 1))), - emptyStmt(false, p(1, 3))) - }) - - expect(t, `1 -`, func(p pfn) []ast.Stmt { - return stmts( - exprStmt(intLit(1, p(1, 1)))) - }) - - expect(t, `1 -;`, func(p pfn) []ast.Stmt { - return stmts( - exprStmt(intLit(1, p(1, 1))), - emptyStmt(false, p(2, 1))) - }) - - expect(t, `1; -;`, func(p pfn) []ast.Stmt { - return stmts( - exprStmt(intLit(1, p(1, 1))), - emptyStmt(false, p(2, 1))) - }) -} diff --git a/compiler/parser/parser_string_test.go b/compiler/parser/parser_string_test.go deleted file mode 100644 index 712a946..0000000 --- a/compiler/parser/parser_string_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package parser_test - -import ( - "testing" - - "github.com/d5/tengo/compiler/ast" - "github.com/d5/tengo/compiler/token" -) - -func TestString(t *testing.T) { - expect(t, `a = "foo\nbar"`, func(p pfn) []ast.Stmt { - return stmts( - assignStmt( - exprs(ident("a", p(1, 1))), - exprs(stringLit("foo\nbar", p(1, 5))), - token.Assign, - p(1, 3))) - }) - - expect(t, "a = `raw string`", func(p pfn) []ast.Stmt { - return stmts( - assignStmt( - exprs(ident("a", p(1, 1))), - exprs(stringLit("raw string", p(1, 5))), - token.Assign, - p(1, 3))) - }) -} diff --git a/compiler/parser/parser_test.go b/compiler/parser/parser_test.go deleted file mode 100644 index dbd47a7..0000000 --- a/compiler/parser/parser_test.go +++ /dev/null @@ -1,483 +0,0 @@ -package parser_test - -import ( - "fmt" - "reflect" - "strings" - "testing" - - "github.com/d5/tengo/assert" - "github.com/d5/tengo/compiler/ast" - "github.com/d5/tengo/compiler/parser" - "github.com/d5/tengo/compiler/source" - "github.com/d5/tengo/compiler/token" -) - -type pfn func(int, int) source.Pos // position conversion function -type expectedFn func(pos pfn) []ast.Stmt // callback function to return expected results - -type tracer struct { - out []string -} - -func (o *tracer) Write(p []byte) (n int, err error) { - o.out = append(o.out, string(p)) - return len(p), nil -} - -//type slowPrinter struct { -//} -// -//func (o *slowPrinter) Write(p []byte) (n int, err error) { -// fmt.Print(string(p)) -// time.Sleep(25 * time.Millisecond) -// return len(p), nil -//} - -func expect(t *testing.T, input string, fn expectedFn) (ok bool) { - testFileSet := source.NewFileSet() - testFile := testFileSet.AddFile("test", -1, len(input)) - - defer func() { - if !ok { - // print trace - tr := &tracer{} - p := parser.NewParser(testFile, []byte(input), tr) - actual, _ := p.ParseFile() - if actual != nil { - t.Logf("Parsed:\n%s", actual.String()) - } - t.Logf("Trace:\n%s", strings.Join(tr.out, "")) - } - }() - - p := parser.NewParser(testFile, []byte(input), nil) - actual, err := p.ParseFile() - if !assert.NoError(t, err) { - return - } - - expected := fn(func(line, column int) source.Pos { - return source.Pos(int(testFile.LineStart(line)) + (column - 1)) - }) - - if !assert.Equal(t, len(expected), len(actual.Stmts)) { - return - } - - for i := 0; i < len(expected); i++ { - if !equalStmt(t, expected[i], actual.Stmts[i]) { - return - } - } - - ok = true - - return -} - -func expectError(t *testing.T, input string) (ok bool) { - testFileSet := source.NewFileSet() - testFile := testFileSet.AddFile("test", -1, len(input)) - - defer func() { - if !ok { - // print trace - tr := &tracer{} - p := parser.NewParser(testFile, []byte(input), tr) - _, _ = p.ParseFile() - t.Logf("Trace:\n%s", strings.Join(tr.out, "")) - } - }() - - p := parser.NewParser(testFile, []byte(input), nil) - _, err := p.ParseFile() - if !assert.Error(t, err) { - return - } - - ok = true - - return -} - -func expectString(t *testing.T, input, expected string) (ok bool) { - defer func() { - if !ok { - // print trace - tr := &tracer{} - _, _ = parser.ParseSource("test", []byte(input), tr) - t.Logf("Trace:\n%s", strings.Join(tr.out, "")) - } - }() - - actual, err := parser.ParseSource("test", []byte(input), nil) - if !assert.NoError(t, err) { - return - } - - if !assert.Equal(t, expected, actual.String()) { - return - } - - ok = true - - return -} - -//func printTrace(input string) { -// testFileSet := source.NewFileSet() -// testFile := testFileSet.AddFile("", -1, len(input)) -// -// _, _ = parser.ParseFile(testFile, []byte(input), &slowPrinter{}) -//} - -func stmts(s ...ast.Stmt) []ast.Stmt { - return s -} - -func exprStmt(x ast.Expr) *ast.ExprStmt { - return &ast.ExprStmt{Expr: x} -} - -func assignStmt(lhs, rhs []ast.Expr, token token.Token, pos source.Pos) *ast.AssignStmt { - return &ast.AssignStmt{LHS: lhs, RHS: rhs, Token: token, TokenPos: pos} -} - -func emptyStmt(implicit bool, pos source.Pos) *ast.EmptyStmt { - return &ast.EmptyStmt{Implicit: implicit, Semicolon: pos} -} - -func returnStmt(pos source.Pos, result ast.Expr) *ast.ReturnStmt { - return &ast.ReturnStmt{Result: result, ReturnPos: pos} -} - -func forStmt(init ast.Stmt, cond ast.Expr, post ast.Stmt, body *ast.BlockStmt, pos source.Pos) *ast.ForStmt { - return &ast.ForStmt{Cond: cond, Init: init, Post: post, Body: body, ForPos: pos} -} - -func forInStmt(key, value *ast.Ident, seq ast.Expr, body *ast.BlockStmt, pos source.Pos) *ast.ForInStmt { - return &ast.ForInStmt{Key: key, Value: value, Iterable: seq, Body: body, ForPos: pos} -} - -func ifStmt(init ast.Stmt, cond ast.Expr, body *ast.BlockStmt, elseStmt ast.Stmt, pos source.Pos) *ast.IfStmt { - return &ast.IfStmt{Init: init, Cond: cond, Body: body, Else: elseStmt, IfPos: pos} -} - -func incDecStmt(expr ast.Expr, tok token.Token, pos source.Pos) *ast.IncDecStmt { - return &ast.IncDecStmt{Expr: expr, Token: tok, TokenPos: pos} -} - -func funcType(params *ast.IdentList, pos source.Pos) *ast.FuncType { - return &ast.FuncType{Params: params, FuncPos: pos} -} - -func blockStmt(lbrace, rbrace source.Pos, list ...ast.Stmt) *ast.BlockStmt { - return &ast.BlockStmt{Stmts: list, LBrace: lbrace, RBrace: rbrace} -} - -func ident(name string, pos source.Pos) *ast.Ident { - return &ast.Ident{Name: name, NamePos: pos} -} - -func identList(opening, closing source.Pos, varArgs bool, list ...*ast.Ident) *ast.IdentList { - return &ast.IdentList{VarArgs: varArgs, List: list, LParen: opening, RParen: closing} -} - -func binaryExpr(x, y ast.Expr, op token.Token, pos source.Pos) *ast.BinaryExpr { - return &ast.BinaryExpr{LHS: x, RHS: y, Token: op, TokenPos: pos} -} - -func condExpr(cond, trueExpr, falseExpr ast.Expr, questionPos, colonPos source.Pos) *ast.CondExpr { - return &ast.CondExpr{Cond: cond, True: trueExpr, False: falseExpr, QuestionPos: questionPos, ColonPos: colonPos} -} - -func unaryExpr(x ast.Expr, op token.Token, pos source.Pos) *ast.UnaryExpr { - return &ast.UnaryExpr{Expr: x, Token: op, TokenPos: pos} -} - -func importExpr(moduleName string, pos source.Pos) *ast.ImportExpr { - return &ast.ImportExpr{ModuleName: moduleName, Token: token.Import, TokenPos: pos} -} - -func exprs(list ...ast.Expr) []ast.Expr { - return list -} - -func intLit(value int64, pos source.Pos) *ast.IntLit { - return &ast.IntLit{Value: value, ValuePos: pos} -} - -func floatLit(value float64, pos source.Pos) *ast.FloatLit { - return &ast.FloatLit{Value: value, ValuePos: pos} -} - -func stringLit(value string, pos source.Pos) *ast.StringLit { - return &ast.StringLit{Value: value, ValuePos: pos} -} - -func charLit(value rune, pos source.Pos) *ast.CharLit { - return &ast.CharLit{Value: value, ValuePos: pos, Literal: fmt.Sprintf("'%c'", value)} -} - -func boolLit(value bool, pos source.Pos) *ast.BoolLit { - return &ast.BoolLit{Value: value, ValuePos: pos} -} - -func arrayLit(lbracket, rbracket source.Pos, list ...ast.Expr) *ast.ArrayLit { - return &ast.ArrayLit{LBrack: lbracket, RBrack: rbracket, Elements: list} -} - -func mapElementLit(key string, keyPos source.Pos, colonPos source.Pos, value ast.Expr) *ast.MapElementLit { - return &ast.MapElementLit{Key: key, KeyPos: keyPos, ColonPos: colonPos, Value: value} -} - -func mapLit(lbrace, rbrace source.Pos, list ...*ast.MapElementLit) *ast.MapLit { - return &ast.MapLit{LBrace: lbrace, RBrace: rbrace, Elements: list} -} - -func funcLit(funcType *ast.FuncType, body *ast.BlockStmt) *ast.FuncLit { - return &ast.FuncLit{Type: funcType, Body: body} -} - -func parenExpr(x ast.Expr, lparen, rparen source.Pos) *ast.ParenExpr { - return &ast.ParenExpr{Expr: x, LParen: lparen, RParen: rparen} -} - -func callExpr(f ast.Expr, lparen, rparen source.Pos, args ...ast.Expr) *ast.CallExpr { - return &ast.CallExpr{Func: f, LParen: lparen, RParen: rparen, Args: args} -} - -func indexExpr(x, index ast.Expr, lbrack, rbrack source.Pos) *ast.IndexExpr { - return &ast.IndexExpr{Expr: x, Index: index, LBrack: lbrack, RBrack: rbrack} -} - -func sliceExpr(x, low, high ast.Expr, lbrack, rbrack source.Pos) *ast.SliceExpr { - return &ast.SliceExpr{Expr: x, Low: low, High: high, LBrack: lbrack, RBrack: rbrack} -} - -func errorExpr(pos source.Pos, x ast.Expr, lparen, rparen source.Pos) *ast.ErrorExpr { - return &ast.ErrorExpr{Expr: x, ErrorPos: pos, LParen: lparen, RParen: rparen} -} - -func selectorExpr(x, sel ast.Expr) *ast.SelectorExpr { - return &ast.SelectorExpr{Expr: x, Sel: sel} -} - -func equalStmt(t *testing.T, expected, actual ast.Stmt) bool { - if expected == nil || reflect.ValueOf(expected).IsNil() { - return assert.Nil(t, actual, "expected nil, but got not nil") - } - if !assert.NotNil(t, actual, "expected not nil, but got nil") { - return false - } - if !assert.IsType(t, expected, actual) { - return false - } - - switch expected := expected.(type) { - case *ast.ExprStmt: - return equalExpr(t, expected.Expr, actual.(*ast.ExprStmt).Expr) - case *ast.EmptyStmt: - return assert.Equal(t, expected.Implicit, actual.(*ast.EmptyStmt).Implicit) && - assert.Equal(t, expected.Semicolon, actual.(*ast.EmptyStmt).Semicolon) - case *ast.BlockStmt: - return assert.Equal(t, expected.LBrace, actual.(*ast.BlockStmt).LBrace) && - assert.Equal(t, expected.RBrace, actual.(*ast.BlockStmt).RBrace) && - equalStmts(t, expected.Stmts, actual.(*ast.BlockStmt).Stmts) - case *ast.AssignStmt: - return equalExprs(t, expected.LHS, actual.(*ast.AssignStmt).LHS) && - equalExprs(t, expected.RHS, actual.(*ast.AssignStmt).RHS) && - assert.Equal(t, int(expected.Token), int(actual.(*ast.AssignStmt).Token)) && - assert.Equal(t, int(expected.TokenPos), int(actual.(*ast.AssignStmt).TokenPos)) - case *ast.IfStmt: - return equalStmt(t, expected.Init, actual.(*ast.IfStmt).Init) && - equalExpr(t, expected.Cond, actual.(*ast.IfStmt).Cond) && - equalStmt(t, expected.Body, actual.(*ast.IfStmt).Body) && - equalStmt(t, expected.Else, actual.(*ast.IfStmt).Else) && - assert.Equal(t, expected.IfPos, actual.(*ast.IfStmt).IfPos) - case *ast.IncDecStmt: - return equalExpr(t, expected.Expr, actual.(*ast.IncDecStmt).Expr) && - assert.Equal(t, expected.Token, actual.(*ast.IncDecStmt).Token) && - assert.Equal(t, expected.TokenPos, actual.(*ast.IncDecStmt).TokenPos) - case *ast.ForStmt: - return equalStmt(t, expected.Init, actual.(*ast.ForStmt).Init) && - equalExpr(t, expected.Cond, actual.(*ast.ForStmt).Cond) && - equalStmt(t, expected.Post, actual.(*ast.ForStmt).Post) && - equalStmt(t, expected.Body, actual.(*ast.ForStmt).Body) && - assert.Equal(t, expected.ForPos, actual.(*ast.ForStmt).ForPos) - case *ast.ForInStmt: - return equalExpr(t, expected.Key, actual.(*ast.ForInStmt).Key) && - equalExpr(t, expected.Value, actual.(*ast.ForInStmt).Value) && - equalExpr(t, expected.Iterable, actual.(*ast.ForInStmt).Iterable) && - equalStmt(t, expected.Body, actual.(*ast.ForInStmt).Body) && - assert.Equal(t, expected.ForPos, actual.(*ast.ForInStmt).ForPos) - case *ast.ReturnStmt: - return equalExpr(t, expected.Result, actual.(*ast.ReturnStmt).Result) && - assert.Equal(t, expected.ReturnPos, actual.(*ast.ReturnStmt).ReturnPos) - case *ast.BranchStmt: - return equalExpr(t, expected.Label, actual.(*ast.BranchStmt).Label) && - assert.Equal(t, expected.Token, actual.(*ast.BranchStmt).Token) && - assert.Equal(t, expected.TokenPos, actual.(*ast.BranchStmt).TokenPos) - default: - panic(fmt.Errorf("unknown type: %T", expected)) - } -} - -func equalExpr(t *testing.T, expected, actual ast.Expr) bool { - if expected == nil || reflect.ValueOf(expected).IsNil() { - return assert.Nil(t, actual, "expected nil, but got not nil") - } - if !assert.NotNil(t, actual, "expected not nil, but got nil") { - return false - } - if !assert.IsType(t, expected, actual) { - return false - } - - switch expected := expected.(type) { - case *ast.Ident: - return assert.Equal(t, expected.Name, actual.(*ast.Ident).Name) && - assert.Equal(t, int(expected.NamePos), int(actual.(*ast.Ident).NamePos)) - case *ast.IntLit: - return assert.Equal(t, expected.Value, actual.(*ast.IntLit).Value) && - assert.Equal(t, int(expected.ValuePos), int(actual.(*ast.IntLit).ValuePos)) - case *ast.FloatLit: - return assert.Equal(t, expected.Value, actual.(*ast.FloatLit).Value) && - assert.Equal(t, int(expected.ValuePos), int(actual.(*ast.FloatLit).ValuePos)) - case *ast.BoolLit: - return assert.Equal(t, expected.Value, actual.(*ast.BoolLit).Value) && - assert.Equal(t, int(expected.ValuePos), int(actual.(*ast.BoolLit).ValuePos)) - case *ast.CharLit: - return assert.Equal(t, expected.Value, actual.(*ast.CharLit).Value) && - assert.Equal(t, int(expected.ValuePos), int(actual.(*ast.CharLit).ValuePos)) - case *ast.StringLit: - return assert.Equal(t, expected.Value, actual.(*ast.StringLit).Value) && - assert.Equal(t, int(expected.ValuePos), int(actual.(*ast.StringLit).ValuePos)) - case *ast.ArrayLit: - return assert.Equal(t, expected.LBrack, actual.(*ast.ArrayLit).LBrack) && - assert.Equal(t, expected.RBrack, actual.(*ast.ArrayLit).RBrack) && - equalExprs(t, expected.Elements, actual.(*ast.ArrayLit).Elements) - case *ast.MapLit: - return assert.Equal(t, expected.LBrace, actual.(*ast.MapLit).LBrace) && - assert.Equal(t, expected.RBrace, actual.(*ast.MapLit).RBrace) && - equalMapElements(t, expected.Elements, actual.(*ast.MapLit).Elements) - case *ast.BinaryExpr: - return equalExpr(t, expected.LHS, actual.(*ast.BinaryExpr).LHS) && - equalExpr(t, expected.RHS, actual.(*ast.BinaryExpr).RHS) && - assert.Equal(t, expected.Token, actual.(*ast.BinaryExpr).Token) && - assert.Equal(t, expected.TokenPos, actual.(*ast.BinaryExpr).TokenPos) - case *ast.UnaryExpr: - return equalExpr(t, expected.Expr, actual.(*ast.UnaryExpr).Expr) && - assert.Equal(t, expected.Token, actual.(*ast.UnaryExpr).Token) && - assert.Equal(t, expected.TokenPos, actual.(*ast.UnaryExpr).TokenPos) - case *ast.FuncLit: - return equalFuncType(t, expected.Type, actual.(*ast.FuncLit).Type) && - equalStmt(t, expected.Body, actual.(*ast.FuncLit).Body) - case *ast.CallExpr: - return equalExpr(t, expected.Func, actual.(*ast.CallExpr).Func) && - assert.Equal(t, expected.LParen, actual.(*ast.CallExpr).LParen) && - assert.Equal(t, expected.RParen, actual.(*ast.CallExpr).RParen) && - equalExprs(t, expected.Args, actual.(*ast.CallExpr).Args) - case *ast.ParenExpr: - return equalExpr(t, expected.Expr, actual.(*ast.ParenExpr).Expr) && - assert.Equal(t, expected.LParen, actual.(*ast.ParenExpr).LParen) && - assert.Equal(t, expected.RParen, actual.(*ast.ParenExpr).RParen) - case *ast.IndexExpr: - return equalExpr(t, expected.Expr, actual.(*ast.IndexExpr).Expr) && - equalExpr(t, expected.Index, actual.(*ast.IndexExpr).Index) && - assert.Equal(t, expected.LBrack, actual.(*ast.IndexExpr).LBrack) && - assert.Equal(t, expected.RBrack, actual.(*ast.IndexExpr).RBrack) - case *ast.SliceExpr: - return equalExpr(t, expected.Expr, actual.(*ast.SliceExpr).Expr) && - equalExpr(t, expected.Low, actual.(*ast.SliceExpr).Low) && - equalExpr(t, expected.High, actual.(*ast.SliceExpr).High) && - assert.Equal(t, expected.LBrack, actual.(*ast.SliceExpr).LBrack) && - assert.Equal(t, expected.RBrack, actual.(*ast.SliceExpr).RBrack) - case *ast.SelectorExpr: - return equalExpr(t, expected.Expr, actual.(*ast.SelectorExpr).Expr) && - equalExpr(t, expected.Sel, actual.(*ast.SelectorExpr).Sel) - case *ast.ImportExpr: - return assert.Equal(t, expected.ModuleName, actual.(*ast.ImportExpr).ModuleName) && - assert.Equal(t, int(expected.TokenPos), int(actual.(*ast.ImportExpr).TokenPos)) && - assert.Equal(t, expected.Token, actual.(*ast.ImportExpr).Token) - case *ast.ErrorExpr: - return equalExpr(t, expected.Expr, actual.(*ast.ErrorExpr).Expr) && - assert.Equal(t, int(expected.ErrorPos), int(actual.(*ast.ErrorExpr).ErrorPos)) && - assert.Equal(t, int(expected.LParen), int(actual.(*ast.ErrorExpr).LParen)) && - assert.Equal(t, int(expected.RParen), int(actual.(*ast.ErrorExpr).RParen)) - case *ast.CondExpr: - return equalExpr(t, expected.Cond, actual.(*ast.CondExpr).Cond) && - equalExpr(t, expected.True, actual.(*ast.CondExpr).True) && - equalExpr(t, expected.False, actual.(*ast.CondExpr).False) && - assert.Equal(t, expected.QuestionPos, actual.(*ast.CondExpr).QuestionPos) && - assert.Equal(t, expected.ColonPos, actual.(*ast.CondExpr).ColonPos) - default: - panic(fmt.Errorf("unknown type: %T", expected)) - } -} - -func equalFuncType(t *testing.T, expected, actual *ast.FuncType) bool { - return assert.Equal(t, expected.Params.LParen, actual.Params.LParen) && - assert.Equal(t, expected.Params.RParen, actual.Params.RParen) && - equalIdents(t, expected.Params.List, actual.Params.List) -} - -func equalIdents(t *testing.T, expected, actual []*ast.Ident) bool { - if !assert.Equal(t, len(expected), len(actual)) { - return false - } - - for i := 0; i < len(expected); i++ { - if !equalExpr(t, expected[i], actual[i]) { - return false - } - } - - return true -} - -func equalExprs(t *testing.T, expected, actual []ast.Expr) bool { - if !assert.Equal(t, len(expected), len(actual)) { - return false - } - - for i := 0; i < len(expected); i++ { - if !equalExpr(t, expected[i], actual[i]) { - return false - } - } - - return true -} - -func equalStmts(t *testing.T, expected, actual []ast.Stmt) bool { - if !assert.Equal(t, len(expected), len(actual)) { - return false - } - - for i := 0; i < len(expected); i++ { - if !equalStmt(t, expected[i], actual[i]) { - return false - } - } - - return true -} - -func equalMapElements(t *testing.T, expected, actual []*ast.MapElementLit) bool { - if !assert.Equal(t, len(expected), len(actual)) { - return false - } - - for i := 0; i < len(expected); i++ { - if !assert.Equal(t, expected[i].Key, actual[i].Key) || - !assert.Equal(t, expected[i].KeyPos, actual[i].KeyPos) || - !assert.Equal(t, expected[i].ColonPos, actual[i].ColonPos) || - !equalExpr(t, expected[i].Value, actual[i].Value) { - return false - } - } - - return true -} diff --git a/compiler/parser/sync.go b/compiler/parser/sync.go deleted file mode 100644 index e68d623..0000000 --- a/compiler/parser/sync.go +++ /dev/null @@ -1,12 +0,0 @@ -package parser - -import "github.com/d5/tengo/compiler/token" - -var stmtStart = map[token.Token]bool{ - token.Break: true, - token.Continue: true, - token.For: true, - token.If: true, - token.Return: true, - token.Export: true, -} diff --git a/compiler/scanner/error_handler.go b/compiler/scanner/error_handler.go deleted file mode 100644 index 379f019..0000000 --- a/compiler/scanner/error_handler.go +++ /dev/null @@ -1,6 +0,0 @@ -package scanner - -import "github.com/d5/tengo/compiler/source" - -// ErrorHandler is an error handler for the scanner. -type ErrorHandler func(pos source.FilePos, msg string) diff --git a/compiler/scanner/mode.go b/compiler/scanner/mode.go deleted file mode 100644 index f67ceaf..0000000 --- a/compiler/scanner/mode.go +++ /dev/null @@ -1,10 +0,0 @@ -package scanner - -// Mode represents a scanner mode. -type Mode int - -// List of scanner modes. -const ( - ScanComments Mode = 1 << iota - DontInsertSemis -) diff --git a/compiler/source/file.go b/compiler/source/file.go deleted file mode 100644 index 9e51c9a..0000000 --- a/compiler/source/file.go +++ /dev/null @@ -1,110 +0,0 @@ -package source - -// File represents a source file. -type File struct { - // File set for the file - set *FileSet - // File name as provided to AddFile - Name string - // Pos value range for this file is [base...base+size] - Base int - // File size as provided to AddFile - Size int - // Lines contains the offset of the first character for each line (the first entry is always 0) - Lines []int -} - -// Set returns FileSet. -func (f *File) Set() *FileSet { - return f.set -} - -// LineCount returns the current number of lines. -func (f *File) LineCount() int { - return len(f.Lines) -} - -// AddLine adds a new line. -func (f *File) AddLine(offset int) { - if i := len(f.Lines); (i == 0 || f.Lines[i-1] < offset) && offset < f.Size { - f.Lines = append(f.Lines, offset) - } -} - -// LineStart returns the position of the first character in the line. -func (f *File) LineStart(line int) Pos { - if line < 1 { - panic("illegal line number (line numbering starts at 1)") - } - - if line > len(f.Lines) { - panic("illegal line number") - } - - return Pos(f.Base + f.Lines[line-1]) -} - -// FileSetPos returns the position in the file set. -func (f *File) FileSetPos(offset int) Pos { - if offset > f.Size { - panic("illegal file offset") - } - - return Pos(f.Base + offset) -} - -// Offset translates the file set position into the file offset. -func (f *File) Offset(p Pos) int { - if int(p) < f.Base || int(p) > f.Base+f.Size { - panic("illegal Pos value") - } - - return int(p) - f.Base -} - -// Position translates the file set position into the file position. -func (f *File) Position(p Pos) (pos FilePos) { - if p != NoPos { - if int(p) < f.Base || int(p) > f.Base+f.Size { - panic("illegal Pos value") - } - - pos = f.position(p) - } - - return -} - -func (f *File) position(p Pos) (pos FilePos) { - offset := int(p) - f.Base - pos.Offset = offset - pos.Filename, pos.Line, pos.Column = f.unpack(offset) - - return -} - -func (f *File) unpack(offset int) (filename string, line, column int) { - filename = f.Name - if i := searchInts(f.Lines, offset); i >= 0 { - line, column = i+1, offset-f.Lines[i]+1 - } - - return -} - -func searchInts(a []int, x int) int { - // This function body is a manually inlined version of: - // return sort.Search(len(a), func(i int) bool { return a[i] > x }) - 1 - i, j := 0, len(a) - for i < j { - h := i + (j-i)/2 // avoid overflow when computing h - // i ≤ h < j - if a[h] <= x { - i = h + 1 - } else { - j = h - } - } - - return i - 1 -} diff --git a/compiler/source/file_pos.go b/compiler/source/file_pos.go deleted file mode 100644 index 4055fe6..0000000 --- a/compiler/source/file_pos.go +++ /dev/null @@ -1,47 +0,0 @@ -package source - -import "fmt" - -// FilePos represents a position information in the file. -type FilePos struct { - Filename string // filename, if any - Offset int // offset, starting at 0 - Line int // line number, starting at 1 - Column int // column number, starting at 1 (byte count) -} - -// IsValid returns true if the position is valid. -func (p FilePos) IsValid() bool { - return p.Line > 0 -} - -// String returns a string in one of several forms: -// -// file:line:column valid position with file name -// file:line valid position with file name but no column (column == 0) -// line:column valid position without file name -// line valid position without file name and no column (column == 0) -// file invalid position with file name -// - invalid position without file name -// -func (p FilePos) String() string { - s := p.Filename - - if p.IsValid() { - if s != "" { - s += ":" - } - - s += fmt.Sprintf("%d", p.Line) - - if p.Column != 0 { - s += fmt.Sprintf(":%d", p.Column) - } - } - - if s == "" { - s = "-" - } - - return s -} diff --git a/compiler/source/file_set.go b/compiler/source/file_set.go deleted file mode 100644 index da34236..0000000 --- a/compiler/source/file_set.go +++ /dev/null @@ -1,96 +0,0 @@ -package source - -import ( - "sort" -) - -// FileSet represents a set of source files. -type FileSet struct { - Base int // base offset for the next file - Files []*File // list of files in the order added to the set - LastFile *File // cache of last file looked up -} - -// NewFileSet creates a new file set. -func NewFileSet() *FileSet { - return &FileSet{ - Base: 1, // 0 == NoPos - } -} - -// AddFile adds a new file in the file set. -func (s *FileSet) AddFile(filename string, base, size int) *File { - if base < 0 { - base = s.Base - } - if base < s.Base || size < 0 { - panic("illegal base or size") - } - - f := &File{ - set: s, - Name: filename, - Base: base, - Size: size, - Lines: []int{0}, - } - - base += size + 1 // +1 because EOF also has a position - if base < 0 { - panic("offset overflow (> 2G of source code in file set)") - } - - // add the file to the file set - s.Base = base - s.Files = append(s.Files, f) - s.LastFile = f - - return f -} - -// File returns the file that contains the position p. -// If no such file is found (for instance for p == NoPos), -// the result is nil. -// -func (s *FileSet) File(p Pos) (f *File) { - if p != NoPos { - f = s.file(p) - } - - return -} - -// Position converts a Pos p in the fileset into a FilePos value. -func (s *FileSet) Position(p Pos) (pos FilePos) { - if p != NoPos { - if f := s.file(p); f != nil { - return f.position(p) - } - } - - return -} - -func (s *FileSet) file(p Pos) *File { - // common case: p is in last file - if f := s.LastFile; f != nil && f.Base <= int(p) && int(p) <= f.Base+f.Size { - return f - } - - // p is not in last file - search all files - if i := searchFiles(s.Files, int(p)); i >= 0 { - f := s.Files[i] - - // f.base <= int(p) by definition of searchFiles - if int(p) <= f.Base+f.Size { - s.LastFile = f // race is ok - s.last is only a cache - return f - } - } - - return nil -} - -func searchFiles(a []*File, x int) int { - return sort.Search(len(a), func(i int) bool { return a[i].Base > x }) - 1 -} diff --git a/compiler/symbol.go b/compiler/symbol.go deleted file mode 100644 index bcd5323..0000000 --- a/compiler/symbol.go +++ /dev/null @@ -1,9 +0,0 @@ -package compiler - -// Symbol represents a symbol in the symbol table. -type Symbol struct { - Name string - Scope SymbolScope - Index int - LocalAssigned bool // if the local symbol is assigned at least once -} diff --git a/compiler/symbol_scopes.go b/compiler/symbol_scopes.go deleted file mode 100644 index e0c0d94..0000000 --- a/compiler/symbol_scopes.go +++ /dev/null @@ -1,12 +0,0 @@ -package compiler - -// SymbolScope represents a symbol scope. -type SymbolScope string - -// List of symbol scopes -const ( - ScopeGlobal SymbolScope = "GLOBAL" - ScopeLocal SymbolScope = "LOCAL" - ScopeBuiltin SymbolScope = "BUILTIN" - ScopeFree SymbolScope = "FREE" -) diff --git a/compiler/symbol_table_test.go b/compiler/symbol_table_test.go deleted file mode 100644 index 0bbd3b2..0000000 --- a/compiler/symbol_table_test.go +++ /dev/null @@ -1,123 +0,0 @@ -package compiler_test - -import ( - "testing" - - "github.com/d5/tengo/assert" - "github.com/d5/tengo/compiler" -) - -func TestSymbolTable(t *testing.T) { - /* - GLOBAL - [0] a - [1] b - - LOCAL 1 - [0] d - - LOCAL 2 - [0] e - [1] f - - LOCAL 2 BLOCK 1 - [2] g - [3] h - - LOCAL 2 BLOCK 2 - [2] i - [3] j - [4] k - - LOCAL 1 BLOCK 1 - [1] l - [2] m - [3] n - [4] o - [5] p - - LOCAL 3 - [0] q - [1] r - */ - - global := symbolTable() - assert.Equal(t, globalSymbol("a", 0), global.Define("a")) - assert.Equal(t, globalSymbol("b", 1), global.Define("b")) - - local1 := global.Fork(false) - assert.Equal(t, localSymbol("d", 0), local1.Define("d")) - - local1Block1 := local1.Fork(true) - assert.Equal(t, localSymbol("l", 1), local1Block1.Define("l")) - assert.Equal(t, localSymbol("m", 2), local1Block1.Define("m")) - assert.Equal(t, localSymbol("n", 3), local1Block1.Define("n")) - assert.Equal(t, localSymbol("o", 4), local1Block1.Define("o")) - assert.Equal(t, localSymbol("p", 5), local1Block1.Define("p")) - - local2 := local1.Fork(false) - assert.Equal(t, localSymbol("e", 0), local2.Define("e")) - assert.Equal(t, localSymbol("f", 1), local2.Define("f")) - - local2Block1 := local2.Fork(true) - assert.Equal(t, localSymbol("g", 2), local2Block1.Define("g")) - assert.Equal(t, localSymbol("h", 3), local2Block1.Define("h")) - - local2Block2 := local2.Fork(true) - assert.Equal(t, localSymbol("i", 2), local2Block2.Define("i")) - assert.Equal(t, localSymbol("j", 3), local2Block2.Define("j")) - assert.Equal(t, localSymbol("k", 4), local2Block2.Define("k")) - - local3 := local1Block1.Fork(false) - assert.Equal(t, localSymbol("q", 0), local3.Define("q")) - assert.Equal(t, localSymbol("r", 1), local3.Define("r")) - - assert.Equal(t, 2, global.MaxSymbols()) - assert.Equal(t, 6, local1.MaxSymbols()) - assert.Equal(t, 6, local1Block1.MaxSymbols()) - assert.Equal(t, 5, local2.MaxSymbols()) - assert.Equal(t, 4, local2Block1.MaxSymbols()) - assert.Equal(t, 5, local2Block2.MaxSymbols()) - assert.Equal(t, 2, local3.MaxSymbols()) - - resolveExpect(t, global, "a", globalSymbol("a", 0), 0) - resolveExpect(t, local1, "d", localSymbol("d", 0), 0) - resolveExpect(t, local1, "a", globalSymbol("a", 0), 1) - resolveExpect(t, local3, "a", globalSymbol("a", 0), 3) - resolveExpect(t, local3, "d", freeSymbol("d", 0), 2) - resolveExpect(t, local3, "r", localSymbol("r", 1), 0) - resolveExpect(t, local2Block2, "k", localSymbol("k", 4), 0) - resolveExpect(t, local2Block2, "e", localSymbol("e", 0), 1) - resolveExpect(t, local2Block2, "b", globalSymbol("b", 1), 3) -} - -func symbol(name string, scope compiler.SymbolScope, index int) *compiler.Symbol { - return &compiler.Symbol{ - Name: name, - Scope: scope, - Index: index, - } -} - -func globalSymbol(name string, index int) *compiler.Symbol { - return symbol(name, compiler.ScopeGlobal, index) -} - -func localSymbol(name string, index int) *compiler.Symbol { - return symbol(name, compiler.ScopeLocal, index) -} - -func freeSymbol(name string, index int) *compiler.Symbol { - return symbol(name, compiler.ScopeFree, index) -} - -func symbolTable() *compiler.SymbolTable { - return compiler.NewSymbolTable() -} - -func resolveExpect(t *testing.T, symbolTable *compiler.SymbolTable, name string, expectedSymbol *compiler.Symbol, expectedDepth int) { - actualSymbol, actualDepth, ok := symbolTable.Resolve(name) - assert.True(t, ok) - assert.Equal(t, expectedSymbol, actualSymbol) - assert.Equal(t, expectedDepth, actualDepth) -} diff --git a/compiler/token/keywords.go b/compiler/token/keywords.go deleted file mode 100644 index fd6e9d0..0000000 --- a/compiler/token/keywords.go +++ /dev/null @@ -1,19 +0,0 @@ -package token - -var keywords map[string]Token - -func init() { - keywords = make(map[string]Token) - for i := _keywordBeg + 1; i < _keywordEnd; i++ { - keywords[tokens[i]] = i - } -} - -// Lookup returns corresponding keyword if ident is a keyword. -func Lookup(ident string) Token { - if tok, isKeyword := keywords[ident]; isKeyword { - return tok - } - - return Ident -} diff --git a/compiler_test.go b/compiler_test.go new file mode 100644 index 0000000..0a7e456 --- /dev/null +++ b/compiler_test.go @@ -0,0 +1,1340 @@ +package tengo_test + +import ( + "fmt" + "strings" + "testing" + + "github.com/d5/tengo" + "github.com/d5/tengo/internal" + "github.com/d5/tengo/internal/require" +) + +func TestCompiler_Compile(t *testing.T) { + expectCompile(t, `1 + 2`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpBinaryOp, 11), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + intObject(1), + intObject(2)))) + + expectCompile(t, `1; 2`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + intObject(1), + intObject(2)))) + + expectCompile(t, `1 - 2`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpBinaryOp, 12), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + intObject(1), + intObject(2)))) + + expectCompile(t, `1 * 2`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpBinaryOp, 13), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + intObject(1), + intObject(2)))) + + expectCompile(t, `2 / 1`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpBinaryOp, 14), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + intObject(2), + intObject(1)))) + + expectCompile(t, `true`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpTrue), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray())) + + expectCompile(t, `false`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpFalse), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray())) + + expectCompile(t, `1 > 2`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpBinaryOp, 39), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + intObject(1), + intObject(2)))) + + expectCompile(t, `1 < 2`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpBinaryOp, 39), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + intObject(2), + intObject(1)))) + + expectCompile(t, `1 >= 2`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpBinaryOp, 44), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + intObject(1), + intObject(2)))) + + expectCompile(t, `1 <= 2`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpBinaryOp, 44), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + intObject(2), + intObject(1)))) + + expectCompile(t, `1 == 2`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpEqual), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + intObject(1), + intObject(2)))) + + expectCompile(t, `1 != 2`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpNotEqual), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + intObject(1), + intObject(2)))) + + expectCompile(t, `true == false`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpTrue), + internal.MakeInstruction(internal.OpFalse), + internal.MakeInstruction(internal.OpEqual), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray())) + + expectCompile(t, `true != false`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpTrue), + internal.MakeInstruction(internal.OpFalse), + internal.MakeInstruction(internal.OpNotEqual), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray())) + + expectCompile(t, `-1`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpMinus), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + intObject(1)))) + + expectCompile(t, `!true`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpTrue), + internal.MakeInstruction(internal.OpLNot), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray())) + + expectCompile(t, `if true { 10 }; 3333`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpTrue), // 0000 + internal.MakeInstruction(internal.OpJumpFalsy, 8), // 0001 + internal.MakeInstruction(internal.OpConstant, 0), // 0004 + internal.MakeInstruction(internal.OpPop), // 0007 + internal.MakeInstruction(internal.OpConstant, 1), // 0008 + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), // 0011 + objectsArray( + intObject(10), + intObject(3333)))) + + expectCompile(t, `if (true) { 10 } else { 20 }; 3333;`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpTrue), // 0000 + internal.MakeInstruction(internal.OpJumpFalsy, 11), // 0001 + internal.MakeInstruction(internal.OpConstant, 0), // 0004 + internal.MakeInstruction(internal.OpPop), // 0007 + internal.MakeInstruction(internal.OpJump, 15), // 0008 + internal.MakeInstruction(internal.OpConstant, 1), // 0011 + internal.MakeInstruction(internal.OpPop), // 0014 + internal.MakeInstruction(internal.OpConstant, 2), // 0015 + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), // 0018 + objectsArray( + intObject(10), + intObject(20), + intObject(3333)))) + + expectCompile(t, `"kami"`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + stringObject("kami")))) + + expectCompile(t, `"ka" + "mi"`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpBinaryOp, 11), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + stringObject("ka"), + stringObject("mi")))) + + expectCompile(t, `a := 1; b := 2; a += b`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpSetGlobal, 0), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpSetGlobal, 1), + internal.MakeInstruction(internal.OpGetGlobal, 0), + internal.MakeInstruction(internal.OpGetGlobal, 1), + internal.MakeInstruction(internal.OpBinaryOp, 11), + internal.MakeInstruction(internal.OpSetGlobal, 0), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + intObject(1), + intObject(2)))) + + expectCompile(t, `a := 1; b := 2; a /= b`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpSetGlobal, 0), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpSetGlobal, 1), + internal.MakeInstruction(internal.OpGetGlobal, 0), + internal.MakeInstruction(internal.OpGetGlobal, 1), + internal.MakeInstruction(internal.OpBinaryOp, 14), + internal.MakeInstruction(internal.OpSetGlobal, 0), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + intObject(1), + intObject(2)))) + + expectCompile(t, `[]`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpArray, 0), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray())) + + expectCompile(t, `[1, 2, 3]`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpConstant, 2), + internal.MakeInstruction(internal.OpArray, 3), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + intObject(1), + intObject(2), + intObject(3)))) + + expectCompile(t, `[1 + 2, 3 - 4, 5 * 6]`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpBinaryOp, 11), + internal.MakeInstruction(internal.OpConstant, 2), + internal.MakeInstruction(internal.OpConstant, 3), + internal.MakeInstruction(internal.OpBinaryOp, 12), + internal.MakeInstruction(internal.OpConstant, 4), + internal.MakeInstruction(internal.OpConstant, 5), + internal.MakeInstruction(internal.OpBinaryOp, 13), + internal.MakeInstruction(internal.OpArray, 3), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + intObject(1), + intObject(2), + intObject(3), + intObject(4), + intObject(5), + intObject(6)))) + + expectCompile(t, `{}`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpMap, 0), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray())) + + expectCompile(t, `{a: 2, b: 4, c: 6}`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpConstant, 2), + internal.MakeInstruction(internal.OpConstant, 3), + internal.MakeInstruction(internal.OpConstant, 4), + internal.MakeInstruction(internal.OpConstant, 5), + internal.MakeInstruction(internal.OpMap, 6), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + stringObject("a"), + intObject(2), + stringObject("b"), + intObject(4), + stringObject("c"), + intObject(6)))) + + expectCompile(t, `{a: 2 + 3, b: 5 * 6}`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpConstant, 2), + internal.MakeInstruction(internal.OpBinaryOp, 11), + internal.MakeInstruction(internal.OpConstant, 3), + internal.MakeInstruction(internal.OpConstant, 4), + internal.MakeInstruction(internal.OpConstant, 5), + internal.MakeInstruction(internal.OpBinaryOp, 13), + internal.MakeInstruction(internal.OpMap, 4), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + stringObject("a"), + intObject(2), + intObject(3), + stringObject("b"), + intObject(5), + intObject(6)))) + + expectCompile(t, `[1, 2, 3][1 + 1]`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpConstant, 2), + internal.MakeInstruction(internal.OpArray, 3), + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpBinaryOp, 11), + internal.MakeInstruction(internal.OpIndex), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + intObject(1), + intObject(2), + intObject(3)))) + + expectCompile(t, `{a: 2}[2 - 1]`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpMap, 2), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpConstant, 2), + internal.MakeInstruction(internal.OpBinaryOp, 12), + internal.MakeInstruction(internal.OpIndex), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + stringObject("a"), + intObject(2), + intObject(1)))) + + expectCompile(t, `[1, 2, 3][:]`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpConstant, 2), + internal.MakeInstruction(internal.OpArray, 3), + internal.MakeInstruction(internal.OpNull), + internal.MakeInstruction(internal.OpNull), + internal.MakeInstruction(internal.OpSliceIndex), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + intObject(1), + intObject(2), + intObject(3)))) + + expectCompile(t, `[1, 2, 3][0 : 2]`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpConstant, 2), + internal.MakeInstruction(internal.OpArray, 3), + internal.MakeInstruction(internal.OpConstant, 3), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpSliceIndex), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + intObject(1), + intObject(2), + intObject(3), + intObject(0)))) + + expectCompile(t, `[1, 2, 3][:2]`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpConstant, 2), + internal.MakeInstruction(internal.OpArray, 3), + internal.MakeInstruction(internal.OpNull), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpSliceIndex), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + intObject(1), + intObject(2), + intObject(3)))) + + expectCompile(t, `[1, 2, 3][0:]`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpConstant, 2), + internal.MakeInstruction(internal.OpArray, 3), + internal.MakeInstruction(internal.OpConstant, 3), + internal.MakeInstruction(internal.OpNull), + internal.MakeInstruction(internal.OpSliceIndex), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + intObject(1), + intObject(2), + intObject(3), + intObject(0)))) + + expectCompile(t, `func() { return 5 + 10 }`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 2), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + intObject(5), + intObject(10), + compiledFunction(0, 0, + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpBinaryOp, 11), + internal.MakeInstruction(internal.OpReturn, 1))))) + + expectCompile(t, `func() { 5 + 10 }`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 2), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + intObject(5), + intObject(10), + compiledFunction(0, 0, + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpBinaryOp, 11), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpReturn, 0))))) + + expectCompile(t, `func() { 1; 2 }`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 2), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + intObject(1), + intObject(2), + compiledFunction(0, 0, + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpReturn, 0))))) + + expectCompile(t, `func() { 1; return 2 }`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 2), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + intObject(1), + intObject(2), + compiledFunction(0, 0, + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpReturn, 1))))) + + expectCompile(t, `func() { if(true) { return 1 } else { return 2 } }`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 2), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + intObject(1), + intObject(2), + compiledFunction(0, 0, + internal.MakeInstruction(internal.OpTrue), // 0000 + internal.MakeInstruction(internal.OpJumpFalsy, 9), // 0001 + internal.MakeInstruction(internal.OpConstant, 0), // 0004 + internal.MakeInstruction(internal.OpReturn, 1), // 0007 + internal.MakeInstruction(internal.OpConstant, 1), // 0009 + internal.MakeInstruction(internal.OpReturn, 1))))) // 0012 + + expectCompile(t, `func() { 1; if(true) { 2 } else { 3 }; 4 }`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 4), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + intObject(1), + intObject(2), + intObject(3), + intObject(4), + compiledFunction(0, 0, + internal.MakeInstruction(internal.OpConstant, 0), // 0000 + internal.MakeInstruction(internal.OpPop), // 0003 + internal.MakeInstruction(internal.OpTrue), // 0004 + internal.MakeInstruction(internal.OpJumpFalsy, 15), // 0005 + internal.MakeInstruction(internal.OpConstant, 1), // 0008 + internal.MakeInstruction(internal.OpPop), // 0011 + internal.MakeInstruction(internal.OpJump, 19), // 0012 + internal.MakeInstruction(internal.OpConstant, 2), // 0015 + internal.MakeInstruction(internal.OpPop), // 0018 + internal.MakeInstruction(internal.OpConstant, 3), // 0019 + internal.MakeInstruction(internal.OpPop), // 0022 + internal.MakeInstruction(internal.OpReturn, 0))))) // 0023 + + expectCompile(t, `func() { }`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + compiledFunction(0, 0, + internal.MakeInstruction(internal.OpReturn, 0))))) + + expectCompile(t, `func() { 24 }()`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpCall, 0), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + intObject(24), + compiledFunction(0, 0, + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpReturn, 0))))) + + expectCompile(t, `func() { return 24 }()`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpCall, 0), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + intObject(24), + compiledFunction(0, 0, + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpReturn, 1))))) + + expectCompile(t, `noArg := func() { 24 }; noArg();`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpSetGlobal, 0), + internal.MakeInstruction(internal.OpGetGlobal, 0), + internal.MakeInstruction(internal.OpCall, 0), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + intObject(24), + compiledFunction(0, 0, + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpReturn, 0))))) + + expectCompile(t, `noArg := func() { return 24 }; noArg();`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpSetGlobal, 0), + internal.MakeInstruction(internal.OpGetGlobal, 0), + internal.MakeInstruction(internal.OpCall, 0), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + intObject(24), + compiledFunction(0, 0, + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpReturn, 1))))) + + expectCompile(t, `n := 55; func() { n };`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpSetGlobal, 0), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + intObject(55), + compiledFunction(0, 0, + internal.MakeInstruction(internal.OpGetGlobal, 0), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpReturn, 0))))) + + expectCompile(t, `func() { n := 55; return n }`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + intObject(55), + compiledFunction(1, 0, + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpDefineLocal, 0), + internal.MakeInstruction(internal.OpGetLocal, 0), + internal.MakeInstruction(internal.OpReturn, 1))))) + + expectCompile(t, `func() { a := 55; b := 77; return a + b }`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 2), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + intObject(55), + intObject(77), + compiledFunction(2, 0, + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpDefineLocal, 0), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpDefineLocal, 1), + internal.MakeInstruction(internal.OpGetLocal, 0), + internal.MakeInstruction(internal.OpGetLocal, 1), + internal.MakeInstruction(internal.OpBinaryOp, 11), + internal.MakeInstruction(internal.OpReturn, 1))))) + + expectCompile(t, `f1 := func(a) { return a }; f1(24);`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpSetGlobal, 0), + internal.MakeInstruction(internal.OpGetGlobal, 0), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpCall, 1), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + compiledFunction(1, 1, + internal.MakeInstruction(internal.OpGetLocal, 0), + internal.MakeInstruction(internal.OpReturn, 1)), + intObject(24)))) + + expectCompile(t, `varTest := func(...a) { return a }; varTest(1,2,3);`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpSetGlobal, 0), + internal.MakeInstruction(internal.OpGetGlobal, 0), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpConstant, 2), + internal.MakeInstruction(internal.OpConstant, 3), + internal.MakeInstruction(internal.OpCall, 3), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + compiledFunction(1, 1, + internal.MakeInstruction(internal.OpGetLocal, 0), + internal.MakeInstruction(internal.OpReturn, 1)), + intObject(1), intObject(2), intObject(3)))) + + expectCompile(t, `f1 := func(a, b, c) { a; b; return c; }; f1(24, 25, 26);`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpSetGlobal, 0), + internal.MakeInstruction(internal.OpGetGlobal, 0), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpConstant, 2), + internal.MakeInstruction(internal.OpConstant, 3), + internal.MakeInstruction(internal.OpCall, 3), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + compiledFunction(3, 3, + internal.MakeInstruction(internal.OpGetLocal, 0), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpGetLocal, 1), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpGetLocal, 2), + internal.MakeInstruction(internal.OpReturn, 1)), + intObject(24), + intObject(25), + intObject(26)))) + + expectCompile(t, `func() { n := 55; n = 23; return n }`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 2), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + intObject(55), + intObject(23), + compiledFunction(1, 0, + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpDefineLocal, 0), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpSetLocal, 0), + internal.MakeInstruction(internal.OpGetLocal, 0), + internal.MakeInstruction(internal.OpReturn, 1))))) + expectCompile(t, `len([]);`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpGetBuiltin, 0), + internal.MakeInstruction(internal.OpArray, 0), + internal.MakeInstruction(internal.OpCall, 1), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray())) + + expectCompile(t, `func() { return len([]) }`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + compiledFunction(0, 0, + internal.MakeInstruction(internal.OpGetBuiltin, 0), + internal.MakeInstruction(internal.OpArray, 0), + internal.MakeInstruction(internal.OpCall, 1), + internal.MakeInstruction(internal.OpReturn, 1))))) + + expectCompile(t, `func(a) { func(b) { return a + b } }`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + compiledFunction(1, 1, + internal.MakeInstruction(internal.OpGetFree, 0), + internal.MakeInstruction(internal.OpGetLocal, 0), + internal.MakeInstruction(internal.OpBinaryOp, 11), + internal.MakeInstruction(internal.OpReturn, 1)), + compiledFunction(1, 1, + internal.MakeInstruction(internal.OpGetLocalPtr, 0), + internal.MakeInstruction(internal.OpClosure, 0, 1), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpReturn, 0))))) + + expectCompile(t, ` +func(a) { + return func(b) { + return func(c) { + return a + b + c + } + } +}`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 2), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + compiledFunction(1, 1, + internal.MakeInstruction(internal.OpGetFree, 0), + internal.MakeInstruction(internal.OpGetFree, 1), + internal.MakeInstruction(internal.OpBinaryOp, 11), + internal.MakeInstruction(internal.OpGetLocal, 0), + internal.MakeInstruction(internal.OpBinaryOp, 11), + internal.MakeInstruction(internal.OpReturn, 1)), + compiledFunction(1, 1, + internal.MakeInstruction(internal.OpGetFreePtr, 0), + internal.MakeInstruction(internal.OpGetLocalPtr, 0), + internal.MakeInstruction(internal.OpClosure, 0, 2), + internal.MakeInstruction(internal.OpReturn, 1)), + compiledFunction(1, 1, + internal.MakeInstruction(internal.OpGetLocalPtr, 0), + internal.MakeInstruction(internal.OpClosure, 1, 1), + internal.MakeInstruction(internal.OpReturn, 1))))) + + expectCompile(t, ` +g := 55; + +func() { + a := 66; + + return func() { + b := 77; + + return func() { + c := 88; + + return g + a + b + c; + } + } +}`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpSetGlobal, 0), + internal.MakeInstruction(internal.OpConstant, 6), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + intObject(55), + intObject(66), + intObject(77), + intObject(88), + compiledFunction(1, 0, + internal.MakeInstruction(internal.OpConstant, 3), + internal.MakeInstruction(internal.OpDefineLocal, 0), + internal.MakeInstruction(internal.OpGetGlobal, 0), + internal.MakeInstruction(internal.OpGetFree, 0), + internal.MakeInstruction(internal.OpBinaryOp, 11), + internal.MakeInstruction(internal.OpGetFree, 1), + internal.MakeInstruction(internal.OpBinaryOp, 11), + internal.MakeInstruction(internal.OpGetLocal, 0), + internal.MakeInstruction(internal.OpBinaryOp, 11), + internal.MakeInstruction(internal.OpReturn, 1)), + compiledFunction(1, 0, + internal.MakeInstruction(internal.OpConstant, 2), + internal.MakeInstruction(internal.OpDefineLocal, 0), + internal.MakeInstruction(internal.OpGetFreePtr, 0), + internal.MakeInstruction(internal.OpGetLocalPtr, 0), + internal.MakeInstruction(internal.OpClosure, 4, 2), + internal.MakeInstruction(internal.OpReturn, 1)), + compiledFunction(1, 0, + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpDefineLocal, 0), + internal.MakeInstruction(internal.OpGetLocalPtr, 0), + internal.MakeInstruction(internal.OpClosure, 5, 1), + internal.MakeInstruction(internal.OpReturn, 1))))) + + expectCompile(t, `for i:=0; i<10; i++ {}`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpSetGlobal, 0), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpGetGlobal, 0), + internal.MakeInstruction(internal.OpBinaryOp, 39), + internal.MakeInstruction(internal.OpJumpFalsy, 31), + internal.MakeInstruction(internal.OpGetGlobal, 0), + internal.MakeInstruction(internal.OpConstant, 2), + internal.MakeInstruction(internal.OpBinaryOp, 11), + internal.MakeInstruction(internal.OpSetGlobal, 0), + internal.MakeInstruction(internal.OpJump, 6), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + intObject(0), + intObject(10), + intObject(1)))) + + expectCompile(t, `m := {}; for k, v in m {}`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpMap, 0), + internal.MakeInstruction(internal.OpSetGlobal, 0), + internal.MakeInstruction(internal.OpGetGlobal, 0), + internal.MakeInstruction(internal.OpIteratorInit), + internal.MakeInstruction(internal.OpSetGlobal, 1), + internal.MakeInstruction(internal.OpGetGlobal, 1), + internal.MakeInstruction(internal.OpIteratorNext), + internal.MakeInstruction(internal.OpJumpFalsy, 37), + internal.MakeInstruction(internal.OpGetGlobal, 1), + internal.MakeInstruction(internal.OpIteratorKey), + internal.MakeInstruction(internal.OpSetGlobal, 2), + internal.MakeInstruction(internal.OpGetGlobal, 1), + internal.MakeInstruction(internal.OpIteratorValue), + internal.MakeInstruction(internal.OpSetGlobal, 3), + internal.MakeInstruction(internal.OpJump, 13), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray())) + + expectCompile(t, `a := 0; a == 0 && a != 1 || a < 1`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpSetGlobal, 0), + internal.MakeInstruction(internal.OpGetGlobal, 0), + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpEqual), + internal.MakeInstruction(internal.OpAndJump, 23), + internal.MakeInstruction(internal.OpGetGlobal, 0), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpNotEqual), + internal.MakeInstruction(internal.OpOrJump, 34), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpGetGlobal, 0), + internal.MakeInstruction(internal.OpBinaryOp, 39), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + intObject(0), + intObject(1)))) + + // unknown module name + expectCompileError(t, `import("user1")`, "module 'user1' not found") + + // too many errors + expectCompileError(t, ` +r["x"] = { + @a:1, + @b:1, + @c:1, + @d:1, + @e:1, + @f:1, + @g:1, + @h:1, + @i:1, + @j:1, + @k:1 +} +`, "Parse Error: illegal character U+0040 '@'\n\tat test:3:5 (and 10 more errors)") + + expectCompileError(t, `import("")`, "empty module name") +} + +func TestCompilerErrorReport(t *testing.T) { + expectCompileError(t, `import("user1")`, + "Compile Error: module 'user1' not found\n\tat test:1:1") + + expectCompileError(t, `a = 1`, + "Compile Error: unresolved reference 'a'\n\tat test:1:1") + expectCompileError(t, `a, b := 1, 2`, + "Compile Error: tuple assignment not allowed\n\tat test:1:1") + expectCompileError(t, `a.b := 1`, + "not allowed with selector") + expectCompileError(t, `a:=1; a:=3`, + "Compile Error: 'a' redeclared in this block\n\tat test:1:7") + + expectCompileError(t, `return 5`, + "Compile Error: return not allowed outside function\n\tat test:1:1") + expectCompileError(t, `func() { break }`, + "Compile Error: break not allowed outside loop\n\tat test:1:10") + expectCompileError(t, `func() { continue }`, + "Compile Error: continue not allowed outside loop\n\tat test:1:10") + expectCompileError(t, `func() { export 5 }`, + "Compile Error: export not allowed inside function\n\tat test:1:10") +} + +func TestCompilerDeadCode(t *testing.T) { + expectCompile(t, ` +func() { + a := 4 + return a + + b := 5 // dead code from here + c := a + return b +}`, + bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 2), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + intObject(4), + intObject(5), + compiledFunction(0, 0, + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpDefineLocal, 0), + internal.MakeInstruction(internal.OpGetLocal, 0), + internal.MakeInstruction(internal.OpReturn, 1))))) + + expectCompile(t, ` +func() { + if true { + return 5 + a := 4 // dead code from here + b := a + return b + } else { + return 4 + c := 5 // dead code from here + d := c + return d + } +}`, bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 2), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + intObject(5), + intObject(4), + compiledFunction(0, 0, + internal.MakeInstruction(internal.OpTrue), + internal.MakeInstruction(internal.OpJumpFalsy, 9), + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpReturn, 1), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpReturn, 1))))) + + expectCompile(t, ` +func() { + a := 1 + for { + if a == 5 { + return 10 + } + 5 + 5 + return 20 + b := a + return b + } +}`, bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 4), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + intObject(1), + intObject(5), + intObject(10), + intObject(20), + compiledFunction(0, 0, + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpDefineLocal, 0), + internal.MakeInstruction(internal.OpGetLocal, 0), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpEqual), + internal.MakeInstruction(internal.OpJumpFalsy, 19), + internal.MakeInstruction(internal.OpConstant, 2), + internal.MakeInstruction(internal.OpReturn, 1), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpBinaryOp, 11), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpConstant, 3), + internal.MakeInstruction(internal.OpReturn, 1))))) + + expectCompile(t, ` +func() { + if true { + return 5 + a := 4 // dead code from here + b := a + return b + } else { + return 4 + c := 5 // dead code from here + d := c + return d + } +}`, bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 2), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + intObject(5), + intObject(4), + compiledFunction(0, 0, + internal.MakeInstruction(internal.OpTrue), + internal.MakeInstruction(internal.OpJumpFalsy, 9), + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpReturn, 1), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpReturn, 1))))) +} + +func TestCompilerScopes(t *testing.T) { + expectCompile(t, ` +if a := 1; a { + a = 2 + b := a +} else { + a = 3 + b := a +}`, bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpSetGlobal, 0), + internal.MakeInstruction(internal.OpGetGlobal, 0), + internal.MakeInstruction(internal.OpJumpFalsy, 27), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpSetGlobal, 0), + internal.MakeInstruction(internal.OpGetGlobal, 0), + internal.MakeInstruction(internal.OpSetGlobal, 1), + internal.MakeInstruction(internal.OpJump, 39), + internal.MakeInstruction(internal.OpConstant, 2), + internal.MakeInstruction(internal.OpSetGlobal, 0), + internal.MakeInstruction(internal.OpGetGlobal, 0), + internal.MakeInstruction(internal.OpSetGlobal, 1), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + intObject(1), + intObject(2), + intObject(3)))) + + expectCompile(t, ` +func() { + if a := 1; a { + a = 2 + b := a + } else { + a = 3 + b := a + } +}`, bytecode( + concatInsts( + internal.MakeInstruction(internal.OpConstant, 3), + internal.MakeInstruction(internal.OpPop), + internal.MakeInstruction(internal.OpSuspend)), + objectsArray( + intObject(1), + intObject(2), + intObject(3), + compiledFunction(0, 0, + internal.MakeInstruction(internal.OpConstant, 0), + internal.MakeInstruction(internal.OpDefineLocal, 0), + internal.MakeInstruction(internal.OpGetLocal, 0), + internal.MakeInstruction(internal.OpJumpFalsy, 22), + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpSetLocal, 0), + internal.MakeInstruction(internal.OpGetLocal, 0), + internal.MakeInstruction(internal.OpDefineLocal, 1), + internal.MakeInstruction(internal.OpJump, 31), + internal.MakeInstruction(internal.OpConstant, 2), + internal.MakeInstruction(internal.OpSetLocal, 0), + internal.MakeInstruction(internal.OpGetLocal, 0), + internal.MakeInstruction(internal.OpDefineLocal, 1), + internal.MakeInstruction(internal.OpReturn, 0))))) +} + +func concatInsts(instructions ...[]byte) []byte { + var concat []byte + for _, i := range instructions { + concat = append(concat, i...) + } + return concat +} + +func bytecode( + instructions []byte, + constants []tengo.Object, +) *tengo.Bytecode { + return &tengo.Bytecode{ + FileSet: internal.NewFileSet(), + MainFunction: &tengo.CompiledFunction{Instructions: instructions}, + Constants: constants, + } +} + +func expectCompile( + t *testing.T, + input string, + expected *tengo.Bytecode, +) { + actual, trace, err := traceCompile(input, nil) + + var ok bool + defer func() { + if !ok { + for _, tr := range trace { + t.Log(tr) + } + } + }() + + require.NoError(t, err) + equalBytecode(t, expected, actual) + ok = true +} + +func expectCompileError(t *testing.T, input, expected string) { + _, trace, err := traceCompile(input, nil) + + var ok bool + defer func() { + if !ok { + for _, tr := range trace { + t.Log(tr) + } + } + }() + + require.Error(t, err) + require.True(t, strings.Contains(err.Error(), expected), + "expected error string: %s, got: %s", expected, err.Error()) + ok = true +} + +func equalBytecode(t *testing.T, expected, actual *tengo.Bytecode) { + require.Equal(t, expected.MainFunction, actual.MainFunction) + equalConstants(t, expected.Constants, actual.Constants) +} + +func equalConstants(t *testing.T, expected, actual []tengo.Object) { + require.Equal(t, len(expected), len(actual)) + for i := 0; i < len(expected); i++ { + require.Equal(t, expected[i], actual[i]) + } +} + +type compileTracer struct { + Out []string +} + +func (o *compileTracer) Write(p []byte) (n int, err error) { + o.Out = append(o.Out, string(p)) + return len(p), nil +} + +func traceCompile( + input string, + symbols map[string]tengo.Object, +) (res *tengo.Bytecode, trace []string, err error) { + fileSet := internal.NewFileSet() + file := fileSet.AddFile("test", -1, len(input)) + + p := internal.NewParser(file, []byte(input), nil) + + symTable := internal.NewSymbolTable() + for name := range symbols { + symTable.Define(name) + } + for idx, fn := range tengo.GetAllBuiltinFunctions() { + symTable.DefineBuiltin(idx, fn.Name) + } + + tr := &compileTracer{} + c := tengo.NewCompiler(file, symTable, nil, nil, tr) + parsed, err := p.ParseFile() + if err != nil { + return + } + + err = c.Compile(parsed) + res = c.Bytecode() + res.RemoveDuplicates() + { + trace = append(trace, fmt.Sprintf("Compiler Trace:\n%s", + strings.Join(tr.Out, ""))) + trace = append(trace, fmt.Sprintf("Compiled Constants:\n%s", + strings.Join(res.FormatConstants(), "\n"))) + trace = append(trace, fmt.Sprintf("Compiled Instructions:\n%s\n", + strings.Join(res.FormatInstructions(), "\n"))) + } + if err != nil { + return + } + return +} + +func objectsArray(o ...tengo.Object) []tengo.Object { + return o +} + +func intObject(v int64) *tengo.Int { + return &tengo.Int{Value: v} +} + +func stringObject(v string) *tengo.String { + return &tengo.String{Value: v} +} + +func compiledFunction( + numLocals, numParams int, + insts ...[]byte, +) *tengo.CompiledFunction { + return &tengo.CompiledFunction{ + Instructions: concatInsts(insts...), + NumLocals: numLocals, + NumParameters: numParams, + } +} diff --git a/docs/builtins.md b/docs/builtins.md index a512b6f..0e8e239 100644 --- a/docs/builtins.md +++ b/docs/builtins.md @@ -2,7 +2,9 @@ ## format -Returns a formatted string. The first argument must be a String object. See [this](https://github.com/d5/tengo/blob/master/docs/formatting.md) for more details on formatting. +Returns a formatted string. The first argument must be a String object. See +[this](https://github.com/d5/tengo/blob/master/docs/formatting.md) for more +details on formatting. ```golang a := [1, 2, 3] @@ -11,7 +13,8 @@ s := format("Foo: %v", a) // s == "Foo: [1, 2, 3]" ## len -Returns the number of elements if the given variable is array, string, map, or module map. +Returns the number of elements if the given variable is array, string, map, or +module map. ```golang v := [1, 2, 3] @@ -20,7 +23,8 @@ l := len(v) // l == 3 ## copy -Creates a copy of the given variable. `copy` function calls `Object.Copy` interface method, which is expected to return a deep-copy of the value it holds. +Creates a copy of the given variable. `copy` function calls `Object.Copy` +interface method, which is expected to return a deep-copy of the value it holds. ```golang v1 := [1, 2, 3] @@ -33,7 +37,8 @@ print(v3[1]) // "2"; 'v3' not affected by 'v1' ## append -Appends object(s) to an array (first argument) and returns a new array object. (Like Go's `append` builtin.) Currently, this function takes array type only. +Appends object(s) to an array (first argument) and returns a new array object. +(Like Go's `append` builtin.) Currently, this function takes array type only. ```golang v := [1] @@ -52,14 +57,18 @@ type_name([1, 2, 3]) // array ## string -Tries to convert an object to string object. See [Runtime Types](https://github.com/d5/tengo/blob/master/docs/runtime-types.md) for more details on type conversion. +Tries to convert an object to string object. See +[Runtime Types](https://github.com/d5/tengo/blob/master/docs/runtime-types.md) +for more details on type conversion. ```golang x := string(123) // x == "123" ``` -Optionally it can take the second argument, which will be returned if the first argument cannot be converted to string. Note that the second argument does not have to be string. +Optionally it can take the second argument, which will be returned if the first +argument cannot be converted to string. Note that the second argument does not +have to be string. ```golang v = string(undefined, "foo") // v == "foo" @@ -68,13 +77,17 @@ v = string(undefined, false) // v == false ## int -Tries to convert an object to int object. See [this](https://github.com/d5/tengo/blob/master/docs/runtime-types.md) for more details on type conversion. +Tries to convert an object to int object. See +[this](https://github.com/d5/tengo/blob/master/docs/runtime-types.md) +for more details on type conversion. ```golang v := int("123") // v == 123 ``` -Optionally it can take the second argument, which will be returned if the first argument cannot be converted to int. Note that the second argument does not have to be int. +Optionally it can take the second argument, which will be returned if the first +argument cannot be converted to int. Note that the second argument does not have +to be int. ```golang v = int(undefined, 10) // v == 10 @@ -83,7 +96,9 @@ v = int(undefined, false) // v == false ## bool -Tries to convert an object to bool object. See [this](https://github.com/d5/tengo/blob/master/docs/runtime-types.md) for more details on type conversion. +Tries to convert an object to bool object. See +[this](https://github.com/d5/tengo/blob/master/docs/runtime-types.md) for more +details on type conversion. ```golang v := bool(1) // v == true @@ -91,13 +106,17 @@ v := bool(1) // v == true ## float -Tries to convert an object to float object. See [this](https://github.com/d5/tengo/blob/master/docs/runtime-types.md) for more details on type conversion. +Tries to convert an object to float object. See +[this](https://github.com/d5/tengo/blob/master/docs/runtime-types.md) for more +details on type conversion. ```golang v := float("19.84") // v == 19.84 ``` -Optionally it can take the second argument, which will be returned if the first argument cannot be converted to float. Note that the second argument does not have to be float. +Optionally it can take the second argument, which will be returned if the first +argument cannot be converted to float. Note that the second argument does not +have to be float. ```golang v = float(undefined, 19.84) // v == 19.84 @@ -106,13 +125,17 @@ v = float(undefined, false) // v == false ## char -Tries to convert an object to char object. See [this](https://github.com/d5/tengo/blob/master/docs/runtime-types.md) for more details on type conversion. +Tries to convert an object to char object. See +[this](https://github.com/d5/tengo/blob/master/docs/runtime-types.md) for more +details on type conversion. ```golang v := char(89) // v == 'Y' ``` -Optionally it can take the second argument, which will be returned if the first argument cannot be converted to float. Note that the second argument does not have to be float. +Optionally it can take the second argument, which will be returned if the first +argument cannot be converted to float. Note that the second argument does not +have to be float. ```golang v = char(undefined, 'X') // v == 'X' @@ -121,20 +144,25 @@ v = char(undefined, false) // v == false ## bytes -Tries to convert an object to bytes object. See [this](https://github.com/d5/tengo/blob/master/docs/runtime-types.md) for more details on type conversion. +Tries to convert an object to bytes object. See +[this](https://github.com/d5/tengo/blob/master/docs/runtime-types.md) for more +details on type conversion. ```golang v := bytes("foo") // v == [102 111 111] ``` -Optionally it can take the second argument, which will be returned if the first argument cannot be converted to float. Note that the second argument does not have to be float. +Optionally it can take the second argument, which will be returned if the first +argument cannot be converted to float. Note that the second argument does not +have to be float. ```golang v = bytes(undefined, bytes("foo")) // v == bytes("foo") v = bytes(undefined, false) // v == false ``` -If you pass an int to `bytes()` function, it will create a new byte object with the given size. +If you pass an int to `bytes()` function, it will create a new byte object with +the given size. ```golang v := bytes(100) @@ -182,11 +210,14 @@ Returns `true` if the object's type is undefined. Or it returns `false`. ## is_function -Returns `true` if the object's type is function or closure. Or it returns `false`. Note that `is_function` returns `false` for builtin functions and user-provided callable objects. +Returns `true` if the object's type is function or closure. Or it returns +`false`. Note that `is_function` returns `false` for builtin functions and +user-provided callable objects. ## is_callable -Returns `true` if the object is callable (e.g. function, closure, builtin function, or user-provided callable objects). Or it returns `false`. +Returns `true` if the object is callable (e.g. function, closure, builtin +function, or user-provided callable objects). Or it returns `false`. ## is_array @@ -206,7 +237,8 @@ Returns `true` if the object's type is immutable map. Or it returns `false`. ## is_iterable -Returns `true` if the object's type is iterable: array, immutable array, map, immutable map, string, and bytes are iterable types in Tengo. +Returns `true` if the object's type is iterable: array, immutable array, map, +immutable map, string, and bytes are iterable types in Tengo. ## is_time diff --git a/docs/formatting.md b/docs/formatting.md index eef493f..7eb2a37 100644 --- a/docs/formatting.md +++ b/docs/formatting.md @@ -69,9 +69,12 @@ Maps: {key1:value1 key2:value2 ...} ## Width and Precision: -Width is specified by an optional decimal number immediately preceding the verb. If absent, the width is whatever is necessary to represent the value. +Width is specified by an optional decimal number immediately preceding the verb. +If absent, the width is whatever is necessary to represent the value. -Precision is specified after the (optional) width by a period followed by a decimal number. If no period is present, a default precision is used. A period with no following number specifies a precision of zero. +Precision is specified after the (optional) width by a period followed by a +decimal number. If no period is present, a default precision is used. A period +with no following number specifies a precision of zero. Examples: ``` %f default width, default precision @@ -81,19 +84,32 @@ Examples: %9.f width 9, precision 0 ``` -Width and precision are measured in units of Unicode code points. Either or both of the flags may be replaced with the character '*', causing their values to be obtained from the next operand (preceding the one to format), which must be of type Int. +Width and precision are measured in units of Unicode code points. Either or +both of the flags may be replaced with the character '*', causing their values +to be obtained from the next operand (preceding the one to format), which must +be of type Int. -For most values, width is the minimum number of runes to output, padding the formatted form with spaces if necessary. +For most values, width is the minimum number of runes to output, padding the +formatted form with spaces if necessary. -For Strings and Bytes, however, precision limits the length of the input to be formatted (not the size of the output), truncating if necessary. Normally it is measured in units of Unicode code points, but for these types when formatted with the %x or %X format it is measured in bytes. +For Strings and Bytes, however, precision limits the length of the input to be +formatted (not the size of the output), truncating if necessary. Normally it is +measured in units of Unicode code points, but for these types when formatted +with the %x or %X format it is measured in bytes. -For floating-point values, width sets the minimum width of the field and precision sets the number of places after the decimal, if appropriate, except that for %g/%G precision sets the maximum number of significant digits (trailing zeros are removed). +For floating-point values, width sets the minimum width of the field and +precision sets the number of places after the decimal, if appropriate, except +that for %g/%G precision sets the maximum number of significant digits +(trailing zeros are removed). For example, given 12.345 the format %6.3f prints 12.345 while %.3g prints 12.3. -The default precision for %e, %f and %#g is 6; for %g it is the smallest number of digits necessary to identify the value uniquely. +The default precision for %e, %f and %#g is 6; for %g it is the smallest number +of digits necessary to identify the value uniquely. -For complex numbers, the width and precision apply to the two components independently and the result is parenthesized, so %f applied to 1.2+3.4i produces (1.200000+3.400000i). +For complex numbers, the width and precision apply to the two components +independently and the result is parenthesized, so %f applied to 1.2+3.4i +produces (1.200000+3.400000i). ## Other flags: ``` @@ -113,5 +129,6 @@ for numbers, this moves the padding after the sign ``` Flags are ignored by verbs that do not expect them. -For example there is no alternate decimal format, so %#d and %d behave identically. +For example there is no alternate decimal format, so %#d and %d behave +identically. diff --git a/docs/interoperability.md b/docs/interoperability.md index 39c108a..6c01be3 100644 --- a/docs/interoperability.md +++ b/docs/interoperability.md @@ -11,17 +11,22 @@ ## Using Scripts -Embedding and executing the Tengo code in Go is very easy. At a high level, this process is like: +Embedding and executing the Tengo code in Go is very easy. At a high level, +this process is like: -- create a [Script](https://godoc.org/github.com/d5/tengo/script#Script) instance with your code, -- _optionally_ add some [Script Variables](https://godoc.org/github.com/d5/tengo/script#Variable) to Script, +- create a [Script](https://godoc.org/github.com/d5/tengo#Script) instance with +your code, +- _optionally_ add some +[Script Variables](https://godoc.org/github.com/d5/tengo#Variable) to Script, - compile or directly run the script, -- retrieve _output_ values from the [Compiled](https://godoc.org/github.com/d5/tengo/script#Compiled) instance. +- retrieve _output_ values from the +[Compiled](https://godoc.org/github.com/d5/tengo#Compiled) instance. -The following is an example where a Tengo script is compiled and run with no input/output variables. +The following is an example where a Tengo script is compiled and run with no +input/output variables. ```golang -import "github.com/d5/tengo/script" +import "github.com/d5/tengo" var code = ` reduce := func(seq, fn) { @@ -34,24 +39,26 @@ print(reduce([1, 2, 3], func(x, s) { s += x })) ` func main() { - s := script.New([]byte(code)) + s := tengo.NewScript([]byte(code)) if _, err := s.Run(); err != nil { panic(err) } } ``` -Here's another example where an input variable is added to the script, and, an output variable is accessed through [Variable.Int](https://godoc.org/github.com/d5/tengo/script#Variable.Int) function: +Here's another example where an input variable is added to the script, and, an +output variable is accessed through +[Variable.Int](https://godoc.org/github.com/d5/tengo#Variable.Int) function: ```golang import ( "fmt" - "github.com/d5/tengo/script" + "github.com/d5/tengo" ) func main() { - s := script.New([]byte(`a := b + 20`)) + s := tengo.NewScript([]byte(`a := b + 20`)) // define variable 'b' _ = s.Add("b", 10) @@ -83,13 +90,25 @@ func main() { } ``` -A variable `b` is defined by the user before compilation using [Script.Add](https://godoc.org/github.com/d5/tengo/script#Script.Add) function. Then a compiled bytecode `c` is used to execute the bytecode and get the value of global variables. In this example, the value of global variable `a` is read using [Compiled.Get](https://godoc.org/github.com/d5/tengo/script#Compiled.Get) function. See [documentation](https://godoc.org/github.com/d5/tengo/script#Variable) for the full list of variable value functions. +A variable `b` is defined by the user before compilation using +[Script.Add](https://godoc.org/github.com/d5/tengo#Script.Add) function. Then a +compiled bytecode `c` is used to execute the bytecode and get the value of +global variables. In this example, the value of global variable `a` is read +using [Compiled.Get](https://godoc.org/github.com/d5/tengo#Compiled.Get) +function. See +[documentation](https://godoc.org/github.com/d5/tengo#Variable) for the +full list of variable value functions. -Value of the global variables can be replaced using [Compiled.Set](https://godoc.org/github.com/d5/tengo/script#Compiled.Set) function. But it will return an error if you try to set the value of un-defined global variables _(e.g. trying to set the value of `x` in the example)_. +Value of the global variables can be replaced using +[Compiled.Set](https://godoc.org/github.com/d5/tengo#Compiled.Set) function. +But it will return an error if you try to set the value of un-defined global +variables _(e.g. trying to set the value of `x` in the example)_. ### Type Conversion Table -When adding a Variable _([Script.Add](https://godoc.org/github.com/d5/tengo/script#Script.Add))_, Script converts Go values into Tengo values based on the following conversion table. +When adding a Variable +_([Script.Add](https://godoc.org/github.com/d5/tengo#Script.Add))_, Script +converts Go values into Tengo values based on the following conversion table. | Go Type | Tengo Type | Note | | :--- | :--- | :--- | @@ -113,30 +132,39 @@ When adding a Variable _([Script.Add](https://godoc.org/github.com/d5/tengo/scri ### User Types -Users can add and use a custom user type in Tengo code by implementing [Object](https://godoc.org/github.com/d5/tengo/objects#Object) interface. Tengo runtime will treat the user types in the same way it does to the runtime types with no performance overhead. See [Object Types](https://github.com/d5/tengo/blob/master/docs/objects.md) for more details. +Users can add and use a custom user type in Tengo code by implementing +[Object](https://godoc.org/github.com/d5/tengo#Object) interface. Tengo runtime +will treat the user types in the same way it does to the runtime types with no +performance overhead. See +[Object Types](https://github.com/d5/tengo/blob/master/docs/objects.md) for +more details. ## Sandbox Environments -To securely compile and execute _potentially_ unsafe script code, you can use the following Script functions. +To securely compile and execute _potentially_ unsafe script code, you can use +the following Script functions. #### Script.SetImports(modules *objects.ModuleMap) -SetImports sets the import modules with corresponding names. Script **does not** include any modules by default. You can use this function to include the [Standard Library](https://github.com/d5/tengo/blob/master/docs/stdlib.md). +SetImports sets the import modules with corresponding names. Script **does not** +include any modules by default. You can use this function to include the +[Standard Library](https://github.com/d5/tengo/blob/master/docs/stdlib.md). ```golang -s := script.New([]byte(`math := import("math"); a := math.abs(-19.84)`)) +s := tengo.NewScript([]byte(`math := import("math"); a := math.abs(-19.84)`)) s.SetImports(stdlib.GetModuleMap("math")) // or, to include all stdlib at once s.SetImports(stdlib.GetModuleMap(stdlib.AllModuleNames()...)) ``` -You can also include Tengo's written module using `objects.SourceModule` (which implements `objects.Importable`). +You can also include Tengo's written module using `objects.SourceModule` +(which implements `objects.Importable`). ```golang -s := script.New([]byte(`double := import("double"); a := double(20)`)) +s := tengo.NewScript([]byte(`double := import("double"); a := double(20)`)) -mods := objects.NewModuleMap() +mods := tengo.NewModuleMap() mods.AddSourceModule("double", []byte(`export func(x) { return x * 2 }`)) s.SetImports(mods) ``` @@ -144,31 +172,42 @@ s.SetImports(mods) #### Script.SetMaxAllocs(n int64) -SetMaxAllocs sets the maximum number of object allocations. Note this is a cumulative metric that tracks only the object creations. Set this to a negative number (e.g. `-1`) if you don't need to limit the number of allocations. +SetMaxAllocs sets the maximum number of object allocations. Note this is a +cumulative metric that tracks only the object creations. Set this to a negative +number (e.g. `-1`) if you don't need to limit the number of allocations. #### Script.EnableFileImport(enable bool) -EnableFileImport enables or disables module loading from the local files. It's disabled by default. +EnableFileImport enables or disables module loading from the local files. It's +disabled by default. #### tengo.MaxStringLen -Sets the maximum byte-length of string values. This limit applies to all running VM instances in the process. Also it's not recommended to set or update this value while any VM is executing. +Sets the maximum byte-length of string values. This limit applies to all +running VM instances in the process. Also it's not recommended to set or update +this value while any VM is executing. #### tengo.MaxBytesLen -Sets the maximum length of bytes values. This limit applies to all running VM instances in the process. Also it's not recommended to set or update this value while any VM is executing. +Sets the maximum length of bytes values. This limit applies to all running VM +instances in the process. Also it's not recommended to set or update this value +while any VM is executing. ## Concurrency -A compiled script (`script.Compiled`) can be used to run the code multiple times by a goroutine. If you want to run the compiled script by multiple goroutine, you should use `Compiled.Clone` function to make a copy of Compiled instances. +A compiled script (`Compiled`) can be used to run the code multiple +times by a goroutine. If you want to run the compiled script by multiple +goroutine, you should use `Compiled.Clone` function to make a copy of Compiled +instances. #### Compiled.Clone() -Clone creates a new copy of Compiled instance. Cloned copies are safe for concurrent use by multiple goroutines. +Clone creates a new copy of Compiled instance. Cloned copies are safe for +concurrent use by multiple goroutines. ```golang for i := 0; i < concurrency; i++ { - go func(compiled *script.Compiled) { + go func(compiled *tengo.Compiled) { // inputs _ = compiled.Set("a", rand.Intn(10)) _ = compiled.Set("b", rand.Intn(10)) @@ -187,6 +226,11 @@ for i := 0; i < concurrency; i++ { ## Compiler and VM -Although it's not recommended, you can directly create and run the Tengo [Parser](https://godoc.org/github.com/d5/tengo/compiler/parser#Parser), [Compiler](https://godoc.org/github.com/d5/tengo/compiler#Compiler), and [VM](https://godoc.org/github.com/d5/tengo/runtime#VM) for yourself instead of using Scripts and Script Variables. It's a bit more involved as you have to manage the symbol tables and global variables between them, but, basically that's what Script and Script Variable is doing internally. +Although it's not recommended, you can directly create and run the Tengo +[Compiler](https://godoc.org/github.com/d5/tengo#Compiler), and +[VM](https://godoc.org/github.com/d5/tengo#VM) for yourself instead of using +Scripts and Script Variables. It's a bit more involved as you have to manage +the symbol tables and global variables between them, but, basically that's what +Script and Script Variable is doing internally. _TODO: add more information here_ diff --git a/docs/objects.md b/docs/objects.md index 900dabb..bf22f8f 100644 --- a/docs/objects.md +++ b/docs/objects.md @@ -3,18 +3,14 @@ ## Table of Contents - [Tengo Objects](#tengo-objects) - - [Object Interface](#object-interface) - - [Callable Interface](#callable-interface) - - [Indexable Interface](#indexable-interface) - - [Index-Assignable Interface](#index-assignable-interface) - - [Iterable Interface](#iterable-interface) - - [Iterator Interface](#iterator-interface) - [Runtime Object Types](#runtime-object-types) - [User Object Types](#user-object-types) ## Tengo Objects -In Tengo, all object types _(both [runtime types](#runtime-object-types) and [user types](#user-object-types))_ must implement [Object](https://godoc.org/github.com/d5/tengo/objects#Object) interface. And some types may implement other optional interfaces ([Callable](https://godoc.org/github.com/d5/tengo/objects#Callable), [Indexable](https://godoc.org/github.com/d5/tengo/objects#Indexable), [IndexAssignable](https://godoc.org/github.com/d5/tengo/objects#IndexAssignable), [Iterable](https://godoc.org/github.com/d5/tengo/objects#Iterable)) to support additional language features. +In Tengo, all object types _(both [runtime types](#runtime-object-types) and +[user types](#user-object-types))_ must implement +[Object](https://godoc.org/github.com/d5/tengo#Object) interface. ### Object Interface @@ -22,136 +18,223 @@ In Tengo, all object types _(both [runtime types](#runtime-object-types) and [us TypeName() string ``` -TypeName method should return the name of the type. Type names are not directly used by the runtime _(except when it reports a run-time error)_, but, it is generally a good idea to keep it short but distinguishable from other types. +TypeName method should return the name of the type. Type names are not directly +used by the runtime _(except when it reports a run-time error)_, but, it is +generally a good idea to keep it short but distinguishable from other types. ```golang String() string ``` -String method should return a string representation of the underlying value. The value returned by String method will be used whenever string formatting for the value is required, most commonly when being converted into String value. +String method should return a string representation of the underlying value. +The value returned by String method will be used whenever string formatting for +the value is required, most commonly when being converted into String value. ```golang BinaryOp(op token.Token, rhs Object) (res Object, err error) ``` -In Tengo, a type can overload binary operators (`+`, `-`, `*`, `/`, `%`, `&`, `|`, `^`, `&^`, `>>`, `<<`, `>`, `>=`; _note that `<` and `<=` operators are not overloadable as they're simply implemented by switching left-hand side and right-hand side of `>`/`>=` operator_) by implementing BinaryOp method. BinaryOp method takes the operator `op` and the right-hand side object `rhs`, and, should return a resulting value `res`. +In Tengo, a type can overload binary operators +(`+`, `-`, `*`, `/`, `%`, `&`, `|`, `^`, `&^`, `>>`, `<<`, `>`, `>=`; _note +that `<` and `<=` operators are not overloadable as they're simply implemented +by switching left-hand side and right-hand side of `>`/`>=` operator_) by +implementing BinaryOp method. BinaryOp method takes the operator `op` and the +right-hand side object `rhs`, and, should return a resulting value `res`. **Error value vs runtime error** -If BinaryOp method returns an error `err` (the second return value), it will be treated as a run-time error, which will halt the execution (`VM.Run() error`) and will return the error to the user. All runtime type implementations, for example, will return an `ErrInvalidOperator` error when the given operator is not supported by the type. +If BinaryOp method returns an error `err` (the second return value), it will be +treated as a run-time error, which will halt the execution (`VM.Run() error`) +and will return the error to the user. All runtime type implementations, for +example, will return an `ErrInvalidOperator` error when the given operator is +not supported by the type. -Alternatively the method can return an `Error` value as its result `res` (the first return value), which will not halt the runtime and will be treated like any other values. As a dynamically typed language, the receiver (another expression or statement) can determine how to translate `Error` value returned from binary operator expression. +Alternatively the method can return an `Error` value as its result `res` +(the first return value), which will not halt the runtime and will be treated +like any other values. As a dynamically typed language, the receiver (another +expression or statement) can determine how to translate `Error` value returned +from binary operator expression. ```golang IsFalsy() bool ``` -IsFalsy method should return true if the underlying value is considered to be [falsy](https://github.com/d5/tengo/blob/master/docs/runtime-types.md#objectisfalsy). +IsFalsy method should return true if the underlying value is considered to be +[falsy](https://github.com/d5/tengo/blob/master/docs/runtime-types.md#objectisfalsy). ```golang Equals(o Object) bool ``` -Equals method should return true if the underlying value is considered to be equal to the underlying value of another object `o`. When comparing values of different types, the runtime does not guarantee or force anything, but, it's generally a good idea to make the result consistent. For example, a custom integer type may return true when comparing against String value, but, it should return the same result for the same inputs. +Equals method should return true if the underlying value is considered to be +equal to the underlying value of another object `o`. When comparing values of +different types, the runtime does not guarantee or force anything, but, it's +generally a good idea to make the result consistent. For example, a custom +integer type may return true when comparing against String value, but, it +should return the same result for the same inputs. ```golang Copy() Object ``` -Copy method should return a _new_ copy of the object. Builtin function `copy` uses this method to copy values. Default implementation of all runtime types return a deep-copy values, but, it's not a requirement by the runtime. - - -### Callable Interface - -If the type implements [Callable](https://godoc.org/github.com/d5/tengo/objects#Callable) interface, its values can be invoked as if they were functions. +Copy method should return a _new_ copy of the object. Builtin function `copy` +uses this method to copy values. Default implementation of all runtime types +return a deep-copy values, but, it's not a requirement by the runtime. ```golang -type Callable interface { - Call(args ...Object) (ret Object, err error) -} +IndexGet(index Object) (value Object, err error) ``` -### Indexable Interface +IndexGet should take an index Object and return a result Object or an error for +indexable objects. Indexable is an object that can take an index and return an +object. If a type is indexable, its values support dot selector +(value = object.index) and indexer (value = object[index]) syntax. -If the type implements [Indexable](https://godoc.org/github.com/d5/tengo/objects#Indexable) interface, its values support dot selector (`value = object.index`) and indexer (`value = object[index]`) syntax. +If Object is not indexable, ErrNotIndexable should be returned as error. If nil +is returned as value, it will be converted to Undefined value by the runtime. + +If `IndexGet` returns an error (`err`), the VM will treat it as a run-time +error and ignore the returned value. + +Array and Map implementation forces the type of index Object to be Int and +String respectively, but, it's not a required behavior of the VM. It is +completely okay to take various index types as long as it is consistent. + +By convention, Array or Array-like types and Map or Map-like types return +`Undefined` value when the key does not exist. But, again, this is not a +required behavior. ```golang -type Indexable interface { - IndexGet(index Object) (value Object, err error) -} +IndexSet(index, value Object) error ``` -If `IndexGet` returns an error (`err`), the VM will treat it as a run-time error. +IndexSet should take an index Object and a value Object for index assignable +objects. Index assignable is an object that can take an index and a value on +the left-hand side of the assignment statement. If a type is index assignable, +its values support assignment using dot selector (`object.index = value`) and +indexer (`object[index] = value`) in the assignment statements. -Array and Map implementation forces the type of index Object to be Int and String respectively, but, it's not a required behavior of the VM. It is completely okay to take various index types as long as it is consistent. +If Object is not index assignable, ErrNotIndexAssignable should be returned as +error. If an error is returned, it will be treated as a run-time error. -By convention, Array or Array-like types and Map or Map-like types return `Undefined` value when the key does not exist. But, again, this is not a required behavior. +Array and Map implementation forces the type of index Object to be Int and +String respectively, but, it's not a required behavior of the VM. It is +completely okay to take various index types as long as it is consistent. -### Index-Assignable Interface +#### Callable Objects -If the type implements [IndexAssignable](https://godoc.org/github.com/d5/tengo/objects#IndexAssignable) interface, its values support assignment using dot selector (`object.index = value`) and indexer (`object[index] = value`) in the assignment statements. +If the type is Callable, its values can be invoked as if they were functions. +Two functions need to be implemented for Callable objects. ```golang -type IndexAssignable interface { - IndexSet(index, value Object) error -} +CanCall() bool ``` -Array and Map implementation forces the type of index Object to be Int and String respectively, but, it's not a required behavior of the VM. It is completely okay to take various index types as long as it is consistent. - -### Iterable Interface - -If the type implements [Iterable](https://godoc.org/github.com/d5/tengo/objects#Iterable) interface, its values can be used in `for-in` statements (`for key, value in object { ... }`). +CanCall should return whether the Object can be called. When this function +returns true, the Object is considered Callable. ```golang -type Iterable interface { - Iterate() Iterator -} +Call(args ...Object) (ret Object, err error) ``` -This Iterate method should return another object that implements [Iterator](https://godoc.org/github.com/d5/tengo/objects#Iterator) interface. +Call should take an arbitrary number of arguments and return a return value +and/or an error, which the VM will consider as a run-time error. -#### Iterator Interface +#### Iterable Objects + +If a type is iterable, its values can be used in `for-in` statements +(`for key, value in object { ... }`). Two functions need to be implemented +for Iterable Objects + +```golang +CanIterate() bool +``` + +CanIterate should return whether the Object can be Iterated. + +```golang +Iterate() Iterator +``` + +The Iterate method should return another object that implements +[Iterator](https://godoc.org/github.com/d5/tengo#Iterator) interface. + +### Iterator Interface ```golang Next() bool ``` -Next method should return true if there are more elements to iterate. When used with `for-in` statements, the compiler uses Key and Value methods to populate the current element's key (or index) and value from the object that this iterator represents. The runtime will stop iterating in `for-in` statement when this method returns false. +Next method should return true if there are more elements to iterate. When used +with `for-in` statements, the compiler uses Key and Value methods to populate +the current element's key (or index) and value from the object that this +iterator represents. The runtime will stop iterating in `for-in` statement +when this method returns false. ```golang Key() Object ``` -Key method should return a key (or an index) Object for the current element of the underlying object. It should return the same value until Next method is called again. By convention, iterators for the map or map-like objects returns the String key, and, iterators for array or array-like objects returns the Int index. But, it's not a requirement by the VM. +Key method should return a key (or an index) Object for the current element of +the underlying object. It should return the same value until Next method is +called again. By convention, iterators for the map or map-like objects returns +the String key, and, iterators for array or array-like objects returns the Int +ndex. But, it's not a requirement by the VM. ```golang Value() Object ``` -Value method should return a value Object for the current element of the underlying object. It should return the same value until Next method is called again. +Value method should return a value Object for the current element of the +underlying object. It should return the same value until Next method is called +again. ## Runtime Object Types These are the basic types Tengo runtime supports out of the box: -- Primitive value types: [Int](https://godoc.org/github.com/d5/tengo/objects#Int), [String](https://godoc.org/github.com/d5/tengo/objects#String), [Float](https://godoc.org/github.com/d5/tengo/objects#Float), [Bool](https://godoc.org/github.com/d5/tengo/objects#ArrayIterator), [Char](https://godoc.org/github.com/d5/tengo/objects#Char), [Bytes](https://godoc.org/github.com/d5/tengo/objects#Bytes), [Time](https://godoc.org/github.com/d5/tengo/objects#Time) -- Composite value types: [Array](https://godoc.org/github.com/d5/tengo/objects#Array), [ImmutableArray](https://godoc.org/github.com/d5/tengo/objects#ImmutableArray), [Map](https://godoc.org/github.com/d5/tengo/objects#Map), [ImmutableMap](https://godoc.org/github.com/d5/tengo/objects#ImmutableMap) -- Functions: [CompiledFunction](https://godoc.org/github.com/d5/tengo/objects#CompiledFunction), [BuiltinFunction](https://godoc.org/github.com/d5/tengo/objects#BuiltinFunction), [UserFunction](https://godoc.org/github.com/d5/tengo/objects#UserFunction) -- [Iterators](https://godoc.org/github.com/d5/tengo/objects#Iterator): [StringIterator](https://godoc.org/github.com/d5/tengo/objects#StringIterator), [ArrayIterator](https://godoc.org/github.com/d5/tengo/objects#ArrayIterator), [MapIterator](https://godoc.org/github.com/d5/tengo/objects#MapIterator), [ImmutableMapIterator](https://godoc.org/github.com/d5/tengo/objects#ImmutableMapIterator) -- [Error](https://godoc.org/github.com/d5/tengo/objects#Error) -- [Undefined](https://godoc.org/github.com/d5/tengo/objects#Undefined) -- Other internal objects: [Closure](https://godoc.org/github.com/d5/tengo/objects#Closure), [Break](https://godoc.org/github.com/d5/tengo/objects#Break), [Continue](https://godoc.org/github.com/d5/tengo/objects#Continue), [ReturnValue](https://godoc.org/github.com/d5/tengo/objects#ReturnValue) +- Primitive value types: [Int](https://godoc.org/github.com/d5/tengo#Int), + [String](https://godoc.org/github.com/d5/tengo#String), + [Float](https://godoc.org/github.com/d5/tengo#Float), + [Bool](https://godoc.org/github.com/d5/tengo#ArrayIterator), + [Char](https://godoc.org/github.com/d5/tengo#Char), + [Bytes](https://godoc.org/github.com/d5/tengo#Bytes), + [Time](https://godoc.org/github.com/d5/tengo#Time) +- Composite value types: [Array](https://godoc.org/github.com/d5/tengo#Array), + [ImmutableArray](https://godoc.org/github.com/d5/tengo#ImmutableArray), + [Map](https://godoc.org/github.com/d5/tengo#Map), + [ImmutableMap](https://godoc.org/github.com/d5/tengo#ImmutableMap) +- Functions: + [CompiledFunction](https://godoc.org/github.com/d5/tengo#CompiledFunction), + [BuiltinFunction](https://godoc.org/github.com/d5/tengo#BuiltinFunction), + [UserFunction](https://godoc.org/github.com/d5/tengo#UserFunction) +- [Iterators](https://godoc.org/github.com/d5/tengo#Iterator): + [StringIterator](https://godoc.org/github.com/d5/tengo#StringIterator), + [ArrayIterator](https://godoc.org/github.com/d5/tengo#ArrayIterator), + [MapIterator](https://godoc.org/github.com/d5/tengo#MapIterator), + [ImmutableMapIterator](https://godoc.org/github.com/d5/tengo#ImmutableMapIterator) +- [Error](https://godoc.org/github.com/d5/tengo#Error) +- [Undefined](https://godoc.org/github.com/d5/tengo#Undefined) +- Other internal objects: [Break](https://godoc.org/github.com/d5/tengo#Break), + [Continue](https://godoc.org/github.com/d5/tengo#Continue), + [ReturnValue](https://godoc.org/github.com/d5/tengo#ReturnValue) + +See +[Runtime Types](https://github.com/d5/tengo/blob/master/docs/runtime-types.md) +for more details on these runtime types. -See [Runtime Types](https://github.com/d5/tengo/blob/master/docs/runtime-types.md) for more details on these runtime types. ## User Object Types -Users can easily extend and add their own types by implementing the same [Object](https://godoc.org/github.com/d5/tengo/objects#Object) interface, and, Tengo runtime will treat them in the same way as its runtime types with no performance overhead. +Users can easily extend and add their own types by implementing the same +[Object](https://godoc.org/github.com/d5/tengo#Object) interface and the +default `ObjectImpl` implementation. Tengo runtime will treat them in the +same way as its runtime types with no performance overhead. Here's an example user type implementation, `StringArray`: ```golang type StringArray struct { + tengo.ObjectImpl Value []string } @@ -159,7 +242,7 @@ func (o *StringArray) String() string { return strings.Join(o.Value, ", ") } -func (o *StringArray) BinaryOp(op token.Token, rhs objects.Object) (objects.Object, error) { +func (o *StringArray) BinaryOp(op token.Token, rhs tengo.Object) (tengo.Object, error) { if rhs, ok := rhs.(*StringArray); ok { switch op { case token.Add: @@ -170,14 +253,14 @@ func (o *StringArray) BinaryOp(op token.Token, rhs objects.Object) (objects.Obje } } - return nil, objects.ErrInvalidOperator + return nil, tengo.ErrInvalidOperator } func (o *StringArray) IsFalsy() bool { return len(o.Value) == 0 } -func (o *StringArray) Equals(x objects.Object) bool { +func (o *StringArray) Equals(x tengo.Object) bool { if x, ok := x.(*StringArray); ok { if len(o.Value) != len(x.Value) { return false @@ -195,7 +278,7 @@ func (o *StringArray) Equals(x objects.Object) bool { return false } -func (o *StringArray) Copy() objects.Object { +func (o *StringArray) Copy() tengo.Object { return &StringArray{ Value: append([]string{}, o.Value...), } @@ -206,77 +289,84 @@ func (o *StringArray) TypeName() string { } ``` -You can use a user type via either [Script.Add](https://godoc.org/github.com/d5/tengo/script#Script.Add) or by directly manipulating the symbol table and the global variables. Here's an example code to add `StringArray` to the script: +You can use a user type via either +[Script.Add](https://godoc.org/github.com/d5/tengo#Script.Add) or by directly +manipulating the symbol table and the global variables. Here's an example code +to add `StringArray` to the script: ```golang // script that uses 'my_list' -s := script.New([]byte(` +s := tengo.NewScript([]byte(` print(my_list + "three") `)) myList := &StringArray{Value: []string{"one", "two"}} -s.Add("my_list", myList) // add StringArray value 'my_list' -s.Run() // prints "one, two, three" +s.Add("my_list", myList) // add StringArray value 'my_list' +s.Run() // prints "one, two, three" ``` -It can also implement `Indexable` and `IndexAssinable` interfaces: +It can also implement `IndexGet` and `IndexSet`: ```golang -func (o *StringArray) IndexGet(index objects.Object) (objects.Object, error) { - intIdx, ok := index.(*objects.Int) +func (o *StringArray) IndexGet(index tengo.Object) (tengo.Object, error) { + intIdx, ok := index.(*tengo.Int) if ok { if intIdx.Value >= 0 && intIdx.Value < int64(len(o.Value)) { - return &objects.String{Value: o.Value[intIdx.Value]}, nil + return &tengo.String{Value: o.Value[intIdx.Value]}, nil } - return nil, objects.ErrIndexOutOfBounds + return nil, tengo.ErrIndexOutOfBounds } - strIdx, ok := index.(*objects.String) + strIdx, ok := index.(*tengo.String) if ok { for vidx, str := range o.Value { if strIdx.Value == str { - return &objects.Int{Value: int64(vidx)}, nil + return &tengo.Int{Value: int64(vidx)}, nil } } - return objects.UndefinedValue, nil + return tengo.UndefinedValue, nil } - return nil, objects.ErrInvalidIndexType + return nil, tengo.ErrInvalidIndexType } -func (o *StringArray) IndexSet(index, value objects.Object) error { - strVal, ok := objects.ToString(value) +func (o *StringArray) IndexSet(index, value tengo.Object) error { + strVal, ok := tengo.ToString(value) if !ok { - return objects.ErrInvalidIndexValueType + return tengo.ErrInvalidIndexValueType } - intIdx, ok := index.(*objects.Int) + intIdx, ok := index.(*tengo.Int) if ok { if intIdx.Value >= 0 && intIdx.Value < int64(len(o.Value)) { o.Value[intIdx.Value] = strVal return nil } - return objects.ErrIndexOutOfBounds + return tengo.ErrIndexOutOfBounds } - return objects.ErrInvalidIndexType + return tengo.ErrInvalidIndexType } ``` -If we implement `Callabale` interface: +If we implement `CamCall` and `Call`: ```golang -func (o *StringArray) Call(args ...objects.Object) (ret objects.Object, err error) { +func (o *StringArray) CanCall() bool { + return true +} + +func (o *StringArray) Call(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 1 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - s1, ok := objects.ToString(args[0]) + s1, ok := tengo.ToString(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string", Found: args[0].TypeName(), @@ -285,37 +375,41 @@ func (o *StringArray) Call(args ...objects.Object) (ret objects.Object, err erro for i, v := range o.Value { if v == s1 { - return &objects.Int{Value: int64(i)}, nil + return &tengo.Int{Value: int64(i)}, nil } } - return objects.UndefinedValue, nil + return tengo.UndefinedValue, nil } ``` Then it can be "invoked": ```golang -s := script.New([]byte(` +s := tengo.NewScript([]byte(` print(my_list("two")) `)) myList := &StringArray{Value: []string{"one", "two", "three"}} -s.Add("my_list", myList) // add StringArray value 'my_list' +s.Add("my_list", myList) // add StringArray value 'my_list' s.Run() // prints "1" (index of "two") ``` We can also make `StringArray` iterable: ```golang -func (o *StringArray) Iterate() objects.Iterator { +func (o *StringArray) CanIterate() bool { + return true +} + +func (o *StringArray) Iterate() tengo.Iterator { return &StringArrayIterator{ strArr: o, } } type StringArrayIterator struct { - objectImpl + tengo.ObjectImpl strArr *StringArray idx int } @@ -329,12 +423,20 @@ func (i *StringArrayIterator) Next() bool { return i.idx <= len(i.strArr.Value) } -func (i *StringArrayIterator) Key() objects.Object { - return &objects.Int{Value: int64(i.idx - 1)} +func (i *StringArrayIterator) Key() tengo.Object { + return &tengo.Int{Value: int64(i.idx - 1)} } -func (i *StringArrayIterator) Value() objects.Object { - return &objects.String{Value: i.strArr.Value[i.idx-1]} +func (i *StringArrayIterator) Value() tengo.Object { + return &tengo.String{Value: i.strArr.Value[i.idx-1]} } ``` +### ObjectImpl + +ObjectImpl represents a default Object Implementation. To defined a new value +type, one can embed ObjectImpl in their type declarations to avoid implementing +all non-significant methods. TypeName() and String() methods still need to be +implemented. + + diff --git a/docs/operators.md b/docs/operators.md index e319505..8988196 100644 --- a/docs/operators.md +++ b/docs/operators.md @@ -122,7 +122,8 @@ ### Equality -Test whether two byte array contain the same data. Uses [bytes.Compare](https://golang.org/pkg/bytes/#Compare) internally. +Test whether two byte array contain the same data. Uses +[bytes.Compare](https://golang.org/pkg/bytes/#Compare) internally. - `(bytes) == (bytes) = (bool)`: equality - `(bytes) != (bytes) = (bool)`: inequality @@ -131,7 +132,8 @@ Test whether two byte array contain the same data. Uses [bytes.Compare](https:// ### Equality -Tests whether two times represent the same time instance. Uses [Time.Equal](https://golang.org/pkg/time/#Time.Equal) internally. +Tests whether two times represent the same time instance. Uses +[Time.Equal](https://golang.org/pkg/time/#Time.Equal) internally. - `(time) == (time) = (bool)`: equality - `(time) != (time) = (bool)`: inequality diff --git a/docs/runtime-types.md b/docs/runtime-types.md index e5779f1..3839b6b 100644 --- a/docs/runtime-types.md +++ b/docs/runtime-types.md @@ -9,7 +9,8 @@ - **Array**: objects array (`[]Object` in Go) - **ImmutableArray**: immutable object array (`[]Object` in Go) - **Map**: objects map with string keys (`map[string]Object` in Go) -- **ImmutableMap**: immutable object map with string keys (`map[string]Object` in Go) +- **ImmutableMap**: immutable object map with string keys (`map[string]Object` + in Go) - **Time**: time (`time.Time` in Go) - **Error**: an error with underlying Object value of any type - **Undefined**: undefined @@ -29,7 +30,8 @@ |Error |**X** |"error: ..." |**X** |false|**X**|**X**|**X**|**X**|**X**| - |**X**| |Undefined|**X** |**X**|**X** |false|**X**|**X**|**X**|**X**|**X**|**X**| - | -_* **X**: No conversion; Typed value functions for `script.Variable` will return zero values._ +_* **X**: No conversion; Typed value functions for `Variable` will +return zero values._ _* strconv: converted using Go's conversion functions from `strconv` package._ _* IsFalsy(): use [Object.IsFalsy()](#objectisfalsy) function_ _* String(): use `Object.String()` function_ @@ -37,7 +39,8 @@ _* time.Unix(): use `time.Unix(v, 0)` to convert to Time_ ## Object.IsFalsy() -`Object.IsFalsy()` interface method is used to determine if a given value should evaluate to `false` (e.g. for condition expression of `if` statement). +`Object.IsFalsy()` interface method is used to determine if a given value +should evaluate to `false` (e.g. for condition expression of `if` statement). - **Int**: `n == 0` - **String**: `len(s) == 0` @@ -59,9 +62,11 @@ _* time.Unix(): use `time.Unix(v, 0)` to convert to Time_ - `float(x)`: tries to convert `x` into float; returns `undefined` if failed - `char(x)`: tries to convert `x` into char; returns `undefined` if failed - `bytes(x)`: tries to convert `x` into bytes; returns `undefined` if failed - - `bytes(N)`: as a special case this will create a Bytes variable with the given size `N` (only if `N` is int) + - `bytes(N)`: as a special case this will create a Bytes variable with the + given size `N` (only if `N` is int) - `time(x)`: tries to convert `x` into time; returns `undefined` if failed -- See [Builtins](https://github.com/d5/tengo/blob/master/docs/builtins.md) for the full list of builtin functions. +- See [Builtins](https://github.com/d5/tengo/blob/master/docs/builtins.md) for +the full list of builtin functions. ## Type Checking Builtin Functions @@ -72,10 +77,13 @@ _* time.Unix(): use `time.Unix(v, 0)` to convert to Time_ - `is_char(x)`: returns `true` if `x` is char; `false` otherwise - `is_bytes(x)`: returns `true` if `x` is bytes; `false` otherwise - `is_array(x)`: return `true` if `x` is array; `false` otherwise -- `is_immutable_array(x)`: return `true` if `x` is immutable array; `false` otherwise +- `is_immutable_array(x)`: return `true` if `x` is immutable array; `false` + otherwise - `is_map(x)`: return `true` if `x` is map; `false` otherwise -- `is_immutable_map(x)`: return `true` if `x` is immutable map; `false` otherwise +- `is_immutable_map(x)`: return `true` if `x` is immutable map; `false` + otherwise - `is_time(x)`: return `true` if `x` is time; `false` otherwise - `is_error(x)`: returns `true` if `x` is error; `false` otherwise - `is_undefined(x)`: returns `true` if `x` is undefined; `false` otherwise -- See [Builtins](https://github.com/d5/tengo/blob/master/docs/builtins.md) for the full list of builtin functions. \ No newline at end of file +- See [Builtins](https://github.com/d5/tengo/blob/master/docs/builtins.md) for + the full list of builtin functions. \ No newline at end of file diff --git a/docs/stdlib-base64.md b/docs/stdlib-base64.md index 864cfa7..ec2f3bd 100644 --- a/docs/stdlib-base64.md +++ b/docs/stdlib-base64.md @@ -9,8 +9,11 @@ fmt := import("base64") - `encode(src)`: returns the base64 encoding of src. - `decode(s)`: returns the bytes represented by the base64 string s. - `raw_encode(src)`: returns the base64 encoding of src but omits the padding. -- `raw_decode(s)`: returns the bytes represented by the base64 string s which omits the padding. +- `raw_decode(s)`: returns the bytes represented by the base64 string s which + omits the padding. - `url_encode(src)`: returns the url-base64 encoding of src. - `url_decode(s)`: returns the bytes represented by the url-base64 string s. -- `raw_url_encode(src)`: returns the url-base64 encoding of src but omits the padding. -- `raw_url_decode(s)`: returns the bytes represented by the url-base64 string s which omits the padding. \ No newline at end of file +- `raw_url_encode(src)`: returns the url-base64 encoding of src but omits the + padding. +- `raw_url_decode(s)`: returns the bytes represented by the url-base64 string + s which omits the padding. \ No newline at end of file diff --git a/docs/stdlib-enum.md b/docs/stdlib-enum.md index 033e898..edb23ea 100644 --- a/docs/stdlib-enum.md +++ b/docs/stdlib-enum.md @@ -6,14 +6,37 @@ enum := import("enum") ## Functions -- `all(x, fn) => bool`: returns true if the given function `fn` evaluates to a truthy value on all of the items in `x`. It returns undefined if `x` is not enumerable. -- `any(x, fn) => bool`: returns true if the given function `fn` evaluates to a truthy value on any of the items in `x`. It returns undefined if `x` is not enumerable. -- `chunk(x, size) => [object]`: returns an array of elements split into groups the length of size. If `x` can't be split evenly, the final chunk will be the remaining elements. It returns undefined if `x` is not array. -- `at(x, key) => object`: returns an element at the given index (if `x` is array) or key (if `x` is map). It returns undefined if `x` is not enumerable. -- `each(x, fn)`: iterates over elements of `x` and invokes `fn` for each element. `fn` is invoked with two arguments: `key` and `value`. `key` is an int index if `x` is array. `key` is a string key if `x` is map. It does not iterate and returns undefined if `x` is not enumerable.` -- `filter(x, fn) => [object]`: iterates over elements of `x`, returning an array of all elements `fn` returns truthy for. `fn` is invoked with two arguments: `key` and `value`. `key` is an int index if `x` is array. `key` is a string key if `x` is map. It returns undefined if `x` is not enumerable. -- `find(x, fn) => object`: iterates over elements of `x`, returning value of the first element `fn` returns truthy for. `fn` is invoked with two arguments: `key` and `value`. `key` is an int index if `x` is array. `key` is a string key if `x` is map. It returns undefined if `x` is not enumerable. -- `find_key(x, fn) => int/string`: iterates over elements of `x`, returning key or index of the first element `fn` returns truthy for. `fn` is invoked with two arguments: `key` and `value`. `key` is an int index if `x` is array. `key` is a string key if `x` is map. It returns undefined if `x` is not enumerable. -- `map(x, fn) => [object]`: creates an array of values by running each element in `x` through `fn`. `fn` is invoked with two arguments: `key` and `value`. `key` is an int index if `x` is array. `key` is a string key if `x` is map. It returns undefined if `x` is not enumerable. +- `all(x, fn) => bool`: returns true if the given function `fn` evaluates to a + truthy value on all of the items in `x`. It returns undefined if `x` is not + enumerable. +- `any(x, fn) => bool`: returns true if the given function `fn` evaluates to a + truthy value on any of the items in `x`. It returns undefined if `x` is not + enumerable. +- `chunk(x, size) => [object]`: returns an array of elements split into groups + the length of size. If `x` can't be split evenly, the final chunk will be the + remaining elements. It returns undefined if `x` is not array. +- `at(x, key) => object`: returns an element at the given index (if `x` is + array) or key (if `x` is map). It returns undefined if `x` is not enumerable. +- `each(x, fn)`: iterates over elements of `x` and invokes `fn` for each + element. `fn` is invoked with two arguments: `key` and `value`. `key` is an + int index if `x` is array. `key` is a string key if `x` is map. It does not + iterate and returns undefined if `x` is not enumerable.` +- `filter(x, fn) => [object]`: iterates over elements of `x`, returning an + array of all elements `fn` returns truthy for. `fn` is invoked with two + arguments: `key` and `value`. `key` is an int index if `x` is array. `key` is + a string key if `x` is map. It returns undefined if `x` is not enumerable. +- `find(x, fn) => object`: iterates over elements of `x`, returning value of + the first element `fn` returns truthy for. `fn` is invoked with two + arguments: `key` and `value`. `key` is an int index if `x` is array. `key` is + a string key if `x` is map. It returns undefined if `x` is not enumerable. +- `find_key(x, fn) => int/string`: iterates over elements of `x`, returning key + or index of the first element `fn` returns truthy for. `fn` is invoked with + two arguments: `key` and `value`. `key` is an int index if `x` is array. + `key` is a string key if `x` is map. It returns undefined if `x` is not + enumerable. +- `map(x, fn) => [object]`: creates an array of values by running each element + in `x` through `fn`. `fn` is invoked with two arguments: `key` and `value`. + `key` is an int index if `x` is array. `key` is a string key if `x` is map. + It returns undefined if `x` is not enumerable. - `key(k, _) => object`: returns the first argument. - `value(_, v) => object`: returns the second argument. \ No newline at end of file diff --git a/docs/stdlib-fmt.md b/docs/stdlib-fmt.md index 3d51df7..88564f0 100644 --- a/docs/stdlib-fmt.md +++ b/docs/stdlib-fmt.md @@ -6,7 +6,18 @@ fmt := import("fmt") ## Functions -- `print(args...)`: Prints a string representation of the given variable to the standard output. Unlike Go's `fmt.Print` function, no spaces are added between the operands. -- `println(args...)`: Prints a string representation of the given variable to the standard output with a newline appended. Unlike Go's `fmt.Println` function, no spaces are added between the operands. -- `printf(format, args...)`: Prints a formatted string to the standard output. It does not append the newline character at the end. The first argument must a String object. See [this](https://github.com/d5/tengo/blob/master/docs/formatting.md) for more details on formatting. -- `sprintf(format, args...)`: Returns a formatted string. Alias of the builtin function `format`. The first argument must be a String object. See [this](https://github.com/d5/tengo/blob/master/docs/formatting.md) for more details on formatting. +- `print(args...)`: Prints a string representation of the given variable to the + standard output. Unlike Go's `fmt.Print` function, no spaces are added between + the operands. +- `println(args...)`: Prints a string representation of the given variable to + the standard output with a newline appended. Unlike Go's `fmt.Println` + function, no spaces are added between the operands. +- `printf(format, args...)`: Prints a formatted string to the standard output. + It does not append the newline character at the end. The first argument must + a String object. See + [this](https://github.com/d5/tengo/blob/master/docs/formatting.md) for more + details on formatting. +- `sprintf(format, args...)`: Returns a formatted string. Alias of the builtin + function `format`. The first argument must be a String object. See + [this](https://github.com/d5/tengo/blob/master/docs/formatting.md) for more + details on formatting. diff --git a/docs/stdlib-json.md b/docs/stdlib-json.md index fd87bf1..a175162 100644 --- a/docs/stdlib-json.md +++ b/docs/stdlib-json.md @@ -6,10 +6,15 @@ json := import("json") ## Functions -- `decode(b string/bytes) => object`: Parses the JSON string and returns an object. -- `encode(o object) => bytes`: Returns the JSON string (bytes) of the object. Unlike Go's JSON package, this function does not HTML-escape texts, but, one can use `html_escape` function if needed. -- `indent(b string/bytes) => bytes`: Returns an indented form of input JSON bytes string. -- `html_escape(b string/bytes) => bytes`: Return an HTML-safe form of input JSON bytes string. +- `decode(b string/bytes) => object`: Parses the JSON string and returns an + object. +- `encode(o object) => bytes`: Returns the JSON string (bytes) of the object. + Unlike Go's JSON package, this function does not HTML-escape texts, but, one + can use `html_escape` function if needed. +- `indent(b string/bytes) => bytes`: Returns an indented form of input JSON + bytes string. +- `html_escape(b string/bytes) => bytes`: Return an HTML-safe form of input + JSON bytes string. ## Examples diff --git a/docs/stdlib-math.md b/docs/stdlib-math.md index 9796b9d..c7c8204 100644 --- a/docs/stdlib-math.md +++ b/docs/stdlib-math.md @@ -26,11 +26,14 @@ math := import("math") - `asin(x float) => float`: returns the arcsine, in radians, of x. - `asinh(x float) => float`: returns the inverse hyperbolic sine of x. - `atan(x float) => float`: returns the arctangent, in radians, of x. -- `atan2(y float, xfloat) => float`: returns the arc tangent of y/x, using the signs of the two to determine the quadrant of the return value. +- `atan2(y float, xfloat) => float`: returns the arc tangent of y/x, using the + signs of the two to determine the quadrant of the return value. - `atanh(x float) => float`: returns the inverse hyperbolic tangent of x. - `cbrt(x float) => float`: returns the cube root of x. -- `ceil(x float) => float`: returns the least integer value greater than or equal to x. -- `copysign(x float, y float) => float`: returns a value with the magnitude of x and the sign of y. +- `ceil(x float) => float`: returns the least integer value greater than or + equal to x. +- `copysign(x float, y float) => float`: returns a value with the magnitude of + x and the sign of y. - `cos(x float) => float`: returns the cosine of the radian argument x. - `cosh(x float) => float`: returns the hyperbolic cosine of x. - `dim(x float, y float) => float`: returns the maximum of x-y or 0. @@ -38,31 +41,46 @@ math := import("math") - `erfc(x float) => float`: returns the complementary error function of x. - `exp(x float) => float`: returns e**x, the base-e exponential of x. - `exp2(x float) => float`: returns 2**x, the base-2 exponential of x. -- `expm1(x float) => float`: returns e**x - 1, the base-e exponential of x minus 1. It is more accurate than Exp(x) - 1 when x is near zero. -- `floor(x float) => float`: returns the greatest integer value less than or equal to x. +- `expm1(x float) => float`: returns e**x - 1, the base-e exponential of x + minus 1. It is more accurate than Exp(x) - 1 when x is near zero. +- `floor(x float) => float`: returns the greatest integer value less than or + equal to x. - `gamma(x float) => float`: returns the Gamma function of x. -- `hypot(p float, q float) => float`: returns Sqrt(p * p + q * q), taking care to avoid unnecessary overflow and underflow. +- `hypot(p float, q float) => float`: returns Sqrt(p * p + q * q), taking care + to avoid unnecessary overflow and underflow. - `ilogb(x float) => float`: returns the binary exponent of x as an integer. -- `inf(sign int) => float`: returns positive infinity if sign >= 0, negative infinity if sign < 0. -- `is_inf(f float, sign int) => float`: reports whether f is an infinity, according to sign. If sign > 0, IsInf reports whether f is positive infinity. If sign < 0, IsInf reports whether f is negative infinity. If sign == 0, IsInf reports whether f is either infinity. -- `is_nan(f float) => float`: reports whether f is an IEEE 754 ``not-a-number'' value. -- `j0(x float) => float`: returns the order-zero Bessel function of the first kind. -- `j1(x float) => float`: returns the order-one Bessel function of the first kind. -- `jn(n int, x float) => float`: returns the order-n Bessel function of the first kind. -- `ldexp(frac float, exp int) => float`: is the inverse of frexp. It returns frac × 2**exp. +- `inf(sign int) => float`: returns positive infinity if sign >= 0, negative + infinity if sign < 0. +- `is_inf(f float, sign int) => float`: reports whether f is an infinity, + according to sign. If sign > 0, IsInf reports whether f is positive infinity. + If sign < 0, IsInf reports whether f is negative infinity. If sign == 0, + IsInf reports whether f is either infinity. +- `is_nan(f float) => float`: reports whether f is an IEEE 754 ``not-a-number'' + value. +- `j0(x float) => float`: returns the order-zero Bessel function of the first + kind. +- `j1(x float) => float`: returns the order-one Bessel function of the first + kind. +- `jn(n int, x float) => float`: returns the order-n Bessel function of the + first kind. +- `ldexp(frac float, exp int) => float`: is the inverse of frexp. It returns + frac × 2**exp. - `log(x float) => float`: returns the natural logarithm of x. - `log10(x float) => float`: returns the decimal logarithm of x. -- `log1p(x float) => float`: returns the natural logarithm of 1 plus its argument x. It is more accurate than Log(1 + x) when x is near zero. +- `log1p(x float) => float`: returns the natural logarithm of 1 plus its + argument x. It is more accurate than Log(1 + x) when x is near zero. - `log2(x float) => float`: returns the binary logarithm of x. - `logb(x float) => float`: returns the binary exponent of x. - `max(x float, y float) => float`: returns the larger of x or y. - `min(x float, y float) => float`: returns the smaller of x or y. - `mod(x float, y float) => float`: returns the floating-point remainder of x/y. - `nan() => float`: returns an IEEE 754 ``not-a-number'' value. -- `nextafter(x float, y float) => float`: returns the next representable float64 value after x towards y. +- `nextafter(x float, y float) => float`: returns the next representable + float64 value after x towards y. - `pow(x float, y float) => float`: returns x**y, the base-x exponential of y. - `pow10(n int) => float`: returns 10**n, the base-10 exponential of n. -- `remainder(x float, y float) => float`: returns the IEEE 754 floating-point remainder of x/y. +- `remainder(x float, y float) => float`: returns the IEEE 754 floating-point + remainder of x/y. - `signbit(x float) => float`: returns true if x is negative or negative zero. - `sin(x float) => float`: returns the sine of the radian argument x. - `sinh(x float) => float`: returns the hyperbolic sine of x. @@ -70,6 +88,9 @@ math := import("math") - `tan(x float) => float`: returns the tangent of the radian argument x. - `tanh(x float) => float`: returns the hyperbolic tangent of x. - `trunc(x float) => float`: returns the integer value of x. -- `y0(x float) => float`: returns the order-zero Bessel function of the second kind. -- `y1(x float) => float`: returns the order-one Bessel function of the second kind. -- `yn(n int, x float) => float`: returns the order-n Bessel function of the second kind. \ No newline at end of file +- `y0(x float) => float`: returns the order-zero Bessel function of the second + kind. +- `y1(x float) => float`: returns the order-one Bessel function of the second + kind. +- `yn(n int, x float) => float`: returns the order-n Bessel function of the + second kind. \ No newline at end of file diff --git a/docs/stdlib-os.md b/docs/stdlib-os.md index 934a006..a6ef660 100644 --- a/docs/stdlib-os.md +++ b/docs/stdlib-os.md @@ -39,48 +39,84 @@ os := import("os") ## Functions -- `args() => [string]`: returns command-line arguments, starting with the program name. -- `chdir(dir string) => error`: changes the current working directory to the named directory. -- `chmod(name string, mode int) => error `: changes the mode of the named file to mode. -- `chown(name string, uid int, gid int) => error `: changes the numeric uid and gid of the named file. +- `args() => [string]`: returns command-line arguments, starting with the + program name. +- `chdir(dir string) => error`: changes the current working directory to the + named directory. +- `chmod(name string, mode int) => error `: changes the mode of the named file + to mode. +- `chown(name string, uid int, gid int) => error `: changes the numeric uid and + gid of the named file. - `clearenv()`: deletes all environment variables. -- `environ() => [string] `: returns a copy of strings representing the environment. -- `exit(code int) `: causes the current program to exit with the given status code. -- `expand_env(s string) => string `: replaces ${var} or $var in the string according to the values of the current environment variables. +- `environ() => [string] `: returns a copy of strings representing the + environment. +- `exit(code int) `: causes the current program to exit with the given status + code. +- `expand_env(s string) => string `: replaces ${var} or $var in the string + according to the values of the current environment variables. - `getegid() => int `: returns the numeric effective group id of the caller. -- `getenv(key string) => string `: retrieves the value of the environment variable named by the key. +- `getenv(key string) => string `: retrieves the value of the environment + variable named by the key. - `geteuid() => int `: returns the numeric effective user id of the caller. - `getgid() => int `: returns the numeric group id of the caller. -- `getgroups() => [int]/error `: returns a list of the numeric ids of groups that the caller belongs to. +- `getgroups() => [int]/error `: returns a list of the numeric ids of groups + that the caller belongs to. - `getpagesize() => int `: returns the underlying system's memory page size. - `getpid() => int `: returns the process id of the caller. - `getppid() => int `: returns the process id of the caller's parent. - `getuid() => int `: returns the numeric user id of the caller. -- `getwd() => string/error `: returns a rooted path name corresponding to the current directory. +- `getwd() => string/error `: returns a rooted path name corresponding to the + current directory. - `hostname() => string/error `: returns the host name reported by the kernel. -- `lchown(name string, uid int, gid int) => error `: changes the numeric uid and gid of the named file. -- `link(oldname string, newname string) => error `: creates newname as a hard link to the oldname file. -- `lookup_env(key string) => string/false`: retrieves the value of the environment variable named by the key. -- `mkdir(name string, perm int) => error `: creates a new directory with the specified name and permission bits (before umask). -- `mkdir_all(name string, perm int) => error `: creates a directory named path, along with any necessary parents, and returns nil, or else returns an error. -- `read_file(name string) => bytes/error `: reads the contents of a file into a byte array -- `readlink(name string) => string/error `: returns the destination of the named symbolic link. +- `lchown(name string, uid int, gid int) => error `: changes the numeric uid + and gid of the named file. +- `link(oldname string, newname string) => error `: creates newname as a hard + link to the oldname file. +- `lookup_env(key string) => string/false`: retrieves the value of the + environment variable named by the key. +- `mkdir(name string, perm int) => error `: creates a new directory with the + specified name and permission bits (before umask). +- `mkdir_all(name string, perm int) => error `: creates a directory named path, + along with any necessary parents, and returns nil, or else returns an error. +- `read_file(name string) => bytes/error `: reads the contents of a file into + a byte array +- `readlink(name string) => string/error `: returns the destination of the + named symbolic link. - `remove(name string) => error `: removes the named file or (empty) directory. -- `remove_all(name string) => error `: removes path and any children it contains. -- `rename(oldpath string, newpath string) => error `: renames (moves) oldpath to newpath. -- `setenv(key string, value string) => error `: sets the value of the environment variable named by the key. -- `stat(filename string) => FileInfo/error`: returns a file info structure describing the file -- `symlink(oldname string newname string) => error `: creates newname as a symbolic link to oldname. -- `temp_dir() => string `: returns the default directory to use for temporary files. -- `truncate(name string, size int) => error `: changes the size of the named file. +- `remove_all(name string) => error `: removes path and any children it + contains. +- `rename(oldpath string, newpath string) => error `: renames (moves) oldpath + to newpath. +- `setenv(key string, value string) => error `: sets the value of the + environment variable named by the key. +- `stat(filename string) => FileInfo/error`: returns a file info structure + describing the file +- `symlink(oldname string newname string) => error `: creates newname as a + symbolic link to oldname. +- `temp_dir() => string `: returns the default directory to use for temporary + files. +- `truncate(name string, size int) => error `: changes the size of the named + file. - `unsetenv(key string) => error `: unsets a single environment variable. -- `create(name string) => File/error`: creates the named file with mode 0666 (before umask), truncating it if it already exists. -- `open(name string) => File/error`: opens the named file for reading. If successful, methods on the returned file can be used for reading; the associated file descriptor has mode O_RDONLY. -- `open_file(name string, flag int, perm int) => File/error`: is the generalized open call; most users will use Open or Create instead. It opens the named file with specified flag (O_RDONLY etc.) and perm (before umask), if applicable. -- `find_process(pid int) => Process/error`: looks for a running process by its pid. -- `start_process(name string, argv [string], dir string, env [string]) => Process/error`: starts a new process with the program, arguments and attributes specified by name, argv and attr. The argv slice will become os.Args in the new process, so it normally starts with the program name. -- `exec_look_path(file string) => string/error`: searches for an executable named file in the directories named by the PATH environment variable. -- `exec(name string, args...) => Command/error`: returns the Command to execute the named program with the given arguments. +- `create(name string) => File/error`: creates the named file with mode 0666 + (before umask), truncating it if it already exists. +- `open(name string) => File/error`: opens the named file for reading. If + successful, methods on the returned file can be used for reading; the + associated file descriptor has mode O_RDONLY. +- `open_file(name string, flag int, perm int) => File/error`: is the + generalized open call; most users will use Open or Create instead. It opens + the named file with specified flag (O_RDONLY etc.) and perm (before umask), + if applicable. +- `find_process(pid int) => Process/error`: looks for a running process by its + pid. +- `start_process(name string, argv [string], dir string, env [string]) => Process/error`: + starts a new process with the program, arguments and attributes specified by + name, argv and attr. The argv slice will become os.Args in the new process, + so it normally starts with the program name. +- `exec_look_path(file string) => string/error`: searches for an executable + named file in the directories named by the PATH environment variable. +- `exec(name string, args...) => Command/error`: returns the Command to execute + the named program with the given arguments. ## File @@ -92,17 +128,23 @@ file.close() ``` - `chdir() => true/error`: changes the current working directory to the file, -- `chown(uid int, gid int) => true/error`: changes the numeric uid and gid of the named file. +- `chown(uid int, gid int) => true/error`: changes the numeric uid and gid of + the named file. - `close() => error`: closes the File, rendering it unusable for I/O. - `name() => string`: returns the name of the file as presented to Open. -- `readdirnames(n int) => [string]/error`: reads and returns a slice of names from the directory. +- `readdirnames(n int) => [string]/error`: reads and returns a slice of names + from the directory. - `sync() => error`: commits the current contents of the file to stable storage. - `write(bytes) => int/error`: writes len(b) bytes to the File. -- `write_string(string) => int/error`: is like 'write', but writes the contents of string s rather than a slice of bytes. +- `write_string(string) => int/error`: is like 'write', but writes the contents + of string s rather than a slice of bytes. - `read(bytes) => int/error`: reads up to len(b) bytes from the File. - `stat() => FileInfo/error`: returns a file info structure describing the file - `chmod(mode int) => error`: changes the mode of the file to mode. -- `seek(offset int, whence int) => int/error`: sets the offset for the next Read or Write on file to offset, interpreted according to whence: 0 means relative to the origin of the file, 1 means relative to the current offset, and 2 means relative to the end. +- `seek(offset int, whence int) => int/error`: sets the offset for the next + Read or Write on file to offset, interpreted according to whence: 0 means + relative to the origin of the file, 1 means relative to the current offset, + and 2 means relative to the end. ## Process @@ -112,9 +154,11 @@ proc.wait() ``` - `kill() => error`: causes the Process to exit immediately. -- `release() => error`: releases any resources associated with the process, rendering it unusable in the future. +- `release() => error`: releases any resources associated with the process, + rendering it unusable in the future. - `signal(signal int) => error`: sends a signal to the Process. -- `wait() => ProcessState/error`: waits for the Process to exit, and then returns a ProcessState describing its status and an error, if any. +- `wait() => ProcessState/error`: waits for the Process to exit, and then + returns a ProcessState describing its status and an error, if any. ## ProcessState @@ -127,7 +171,8 @@ pid := stat.pid() - `exited() => bool`: reports whether the program has exited. - `pid() => int`: returns the process id of the exited process. - `string() => string`: returns a string representation of the process. -- `success() => bool`: reports whether the program exited successfully, such as with exit status 0 on Unix. +- `success() => bool`: reports whether the program exited successfully, such as + with exit status 0 on Unix. ```golang cmd := exec.command("echo", ["foo", "bar"]) @@ -144,11 +189,14 @@ output := cmd.output() ## Command -- `combined_output() => bytes/error`: runs the command and returns its combined standard output and standard error. +- `combined_output() => bytes/error`: runs the command and returns its combined + standard output and standard error. - `output() => bytes/error`: runs the command and returns its standard output. - `run() => error`: starts the specified command and waits for it to complete. -- `start() => error`: starts the specified command but does not wait for it to complete. -- `wait() => error`: waits for the command to exit and waits for any copying to stdin or copying from stdout or stderr to complete. +- `start() => error`: starts the specified command but does not wait for it to + complete. +- `wait() => error`: waits for the command to exit and waits for any copying to + stdin or copying from stdout or stderr to complete. - `set_path(path string)`: sets the path of the command to run. - `set_dir(dir string)`: sets the working directory of the process. - `set_env(env [string])`: sets the environment of the process. diff --git a/docs/stdlib-rand.md b/docs/stdlib-rand.md index 94ef805..635eccc 100644 --- a/docs/stdlib-rand.md +++ b/docs/stdlib-rand.md @@ -6,23 +6,45 @@ rand := import("rand") ## Functions -- `seed(seed int)`: uses the provided seed value to initialize the default Source to a deterministic state. -- `exp_float() => float`: returns an exponentially distributed float64 in the range (0, +math.MaxFloat64] with an exponential distribution whose rate parameter (lambda) is 1 and whose mean is 1/lambda (1) from the default Source. -- `float() => float`: returns, as a float64, a pseudo-random number in [0.0,1.0) from the default Source. -- `int() => int`: returns a non-negative pseudo-random 63-bit integer as an int64 from the default Source. -- `intn(n int) => int`: returns, as an int64, a non-negative pseudo-random number in [0,n) from the default Source. It panics if n <= 0. -- `norm_float) => float`: returns a normally distributed float64 in the range [-math.MaxFloat64, +math.MaxFloat64] with standard normal distribution (mean = 0, stddev = 1) from the default Source. -- `perm(n int) => [int]`: returns, as a slice of n ints, a pseudo-random permutation of the integers [0,n) from the default Source. -- `read(p bytes) => int/error`: generates len(p) random bytes from the default Source and writes them into p. It always returns len(p) and a nil error. -- `rand(src_seed int) => Rand`: returns a new Rand that uses random values from src to generate other random values. +- `seed(seed int)`: uses the provided seed value to initialize the default + Source to a deterministic state. +- `exp_float() => float`: returns an exponentially distributed float64 in the + range (0, +math.MaxFloat64] with an exponential distribution whose rate + parameter (lambda) is 1 and whose mean is 1/lambda (1) from the default + Source. +- `float() => float`: returns, as a float64, a pseudo-random number in + [0.0,1.0) from the default Source. +- `int() => int`: returns a non-negative pseudo-random 63-bit integer as an + int64 from the default Source. +- `intn(n int) => int`: returns, as an int64, a non-negative pseudo-random + number in [0,n) from the default Source. It panics if n <= 0. +- `norm_float) => float`: returns a normally distributed float64 in the range + [-math.MaxFloat64, +math.MaxFloat64] with standard normal distribution + (mean = 0, stddev = 1) from the default Source. +- `perm(n int) => [int]`: returns, as a slice of n ints, a pseudo-random + permutation of the integers [0,n) from the default Source. +- `read(p bytes) => int/error`: generates len(p) random bytes from the default + Source and writes them into p. It always returns len(p) and a nil error. +- `rand(src_seed int) => Rand`: returns a new Rand that uses random values from + src to generate other random values. ## Rand -- `seed(seed int)`: uses the provided seed value to initialize the default Source to a deterministic state. -- `exp_float() => float`: returns an exponentially distributed float64 in the range (0, +math.MaxFloat64] with an exponential distribution whose rate parameter (lambda) is 1 and whose mean is 1/lambda (1) from the default Source. -- `float() => float`: returns, as a float64, a pseudo-random number in [0.0,1.0) from the default Source. -- `int() => int`: returns a non-negative pseudo-random 63-bit integer as an int64 from the default Source. -- `intn(n int) => int`: returns, as an int64, a non-negative pseudo-random number in [0,n) from the default Source. It panics if n <= 0. -- `norm_float) => float`: returns a normally distributed float64 in the range [-math.MaxFloat64, +math.MaxFloat64] with standard normal distribution (mean = 0, stddev = 1) from the default Source. -- `perm(n int) => [int]`: returns, as a slice of n ints, a pseudo-random permutation of the integers [0,n) from the default Source. -- `read(p bytes) => int/error`: generates len(p) random bytes from the default Source and writes them into p. It always returns len(p) and a nil error. \ No newline at end of file +- `seed(seed int)`: uses the provided seed value to initialize the default + Source to a deterministic state. +- `exp_float() => float`: returns an exponentially distributed float64 in the + range (0, +math.MaxFloat64] with an exponential distribution whose rate + parameter (lambda) is 1 and whose mean is 1/lambda (1) from the default Source. +- `float() => float`: returns, as a float64, a pseudo-random number in + [0.0,1.0) from the default Source. +- `int() => int`: returns a non-negative pseudo-random 63-bit integer as an + int64 from the default Source. +- `intn(n int) => int`: returns, as an int64, a non-negative pseudo-random + number in [0,n) from the default Source. It panics if n <= 0. +- `norm_float) => float`: returns a normally distributed float64 in the range + [-math.MaxFloat64, +math.MaxFloat64] with standard normal distribution + (mean = 0, stddev = 1) from the default Source. +- `perm(n int) => [int]`: returns, as a slice of n ints, a pseudo-random + permutation of the integers [0,n) from the default Source. +- `read(p bytes) => int/error`: generates len(p) random bytes from the default + Source and writes them into p. It always returns len(p) and a nil error. \ No newline at end of file diff --git a/docs/stdlib-text.md b/docs/stdlib-text.md index eb825f5..99929c5 100644 --- a/docs/stdlib-text.md +++ b/docs/stdlib-text.md @@ -6,57 +6,136 @@ text := import("text") ## Functions -- `re_match(pattern string, text string) => bool/error`: reports whether the string s contains any match of the regular expression pattern. -- `re_find(pattern string, text string, count int) => [[{text: string, begin: int, end: int}]]/undefined`: returns an array holding all matches, each of which is an array of map object that contains matching text, begin and end (exclusive) index. -- `re_replace(pattern string, text string, repl string) => string/error`: returns a copy of src, replacing matches of the pattern with the replacement string repl. -- `re_split(pattern string, text string, count int) => [string]/error`: slices s into substrings separated by the expression and returns a slice of the substrings between those expression matches. -- `re_compile(pattern string) => Regexp/error`: parses a regular expression and returns, if successful, a Regexp object that can be used to match against text. -- `compare(a string, b string) => int`: returns an integer comparing two strings lexicographically. The result will be 0 if a==b, -1 if a < b, and +1 if a > b. -- `contains(s string, substr string) => bool`: reports whether substr is within s. -- `contains_any(s string, chars string) => bool`: reports whether any Unicode code points in chars are within s. -- `count(s string, substr string) => int`: counts the number of non-overlapping instances of substr in s. -- `equal_fold(s string, t string) => bool`: reports whether s and t, interpreted as UTF-8 strings, -- `fields(s string) => [string]`: splits the string s around each instance of one or more consecutive white space characters, as defined by unicode.IsSpace, returning a slice of substrings of s or an empty slice if s contains only white space. -- `has_prefix(s string, prefix string) => bool`: tests whether the string s begins with prefix. -- `has_suffix(s string, suffix string) => bool`: tests whether the string s ends with suffix. -- `index(s string, substr string) => int`: returns the index of the first instance of substr in s, or -1 if substr is not present in s. -- `index_any(s string, chars string) => int`: returns the index of the first instance of any Unicode code point from chars in s, or -1 if no Unicode code point from chars is present in s. -- `join(arr string, sep string) => string`: concatenates the elements of a to create a single string. The separator string sep is placed between elements in the resulting string. -- `last_index(s string, substr string) => int`: returns the index of the last instance of substr in s, or -1 if substr is not present in s. -- `last_index_any(s string, chars string) => int`: returns the index of the last instance of any Unicode code point from chars in s, or -1 if no Unicode code point from chars is present in s. -- `repeat(s string, count int) => string`: returns a new string consisting of count copies of the string s. -- `replace(s string, old string, new string, n int) => string`: returns a copy of the string s with the first n non-overlapping instances of old replaced by new. -- `substr(s string, lower int, upper int) => string => string`: returns a substring of the string s specified by the lower and upper parameters. -- `split(s string, sep string) => [string]`: slices s into all substrings separated by sep and returns a slice of the substrings between those separators. -- `split_after(s string, sep string) => [string]`: slices s into all substrings after each instance of sep and returns a slice of those substrings. -- `split_after_n(s string, sep string, n int) => [string]`: slices s into substrings after each instance of sep and returns a slice of those substrings. -- `split_n(s string, sep string, n int) => [string]`: slices s into substrings separated by sep and returns a slice of the substrings between those separators. -- `title(s string) => string`: returns a copy of the string s with all Unicode letters that begin words mapped to their title case. -- `to_lower(s string) => string`: returns a copy of the string s with all Unicode letters mapped to their lower case. -- `to_title(s string) => string`: returns a copy of the string s with all Unicode letters mapped to their title case. -- `to_upper(s string) => string`: returns a copy of the string s with all Unicode letters mapped to their upper case. -- `pad_left(s string, pad_len int, pad_with string) => string`: returns a copy of the string s padded on the left with the contents of the string pad_with to length pad_len. If pad_with is not specified, white space is used as the default padding. -- `pad_right(s string, pad_len int, pad_with string) => string`: returns a copy of the string s padded on the right with the contents of the string pad_with to length pad_len. If pad_with is not specified, white space is used as the default padding. -- `trim(s string, cutset string) => string`: returns a slice of the string s with all leading and trailing Unicode code points contained in cutset removed. -- `trim_left(s string, cutset string) => string`: returns a slice of the string s with all leading Unicode code points contained in cutset removed. -- `trim_prefix(s string, prefix string) => string`: returns s without the provided leading prefix string. -- `trim_right(s string, cutset string) => string`: returns a slice of the string s, with all trailing Unicode code points contained in cutset removed. -- `trim_space(s string) => string`: returns a slice of the string s, with all leading and trailing white space removed, as defined by Unicode. -- `trim_suffix(s string, suffix string) => string`: returns s without the provided trailing suffix string. -- `atoi(str string) => int/error`: returns the result of ParseInt(s, 10, 0) converted to type int. -- `format_bool(b bool) => string`: returns "true" or "false" according to the value of b. -- `format_float(f float, fmt string, prec int, bits int) => string`: converts the floating-point number f to a string, according to the format fmt and precision prec. -- `format_int(i int, base int) => string`: returns the string representation of i in the given base, for 2 <= base <= 36. The result uses the lower-case letters 'a' to 'z' for digit values >= 10. +- `re_match(pattern string, text string) => bool/error`: reports whether the + string s contains any match of the regular expression pattern. +- `re_find(pattern string, text string, count int) => [[{text: string, begin: int, end: int}]]/undefined`: + returns an array holding all matches, each of which is an array of map object + that contains matching text, begin and end (exclusive) index. +- `re_replace(pattern string, text string, repl string) => string/error`: + returns a copy of src, replacing matches of the pattern with the replacement + string repl. +- `re_split(pattern string, text string, count int) => [string]/error`: slices + s into substrings separated by the expression and returns a slice of the + substrings between those expression matches. +- `re_compile(pattern string) => Regexp/error`: parses a regular expression and + returns, if successful, a Regexp object that can be used to match against + text. +- `compare(a string, b string) => int`: returns an integer comparing two + strings lexicographically. The result will be 0 if a==b, -1 if a < b, and +1 + if a > b. +- `contains(s string, substr string) => bool`: reports whether substr is within + s. +- `contains_any(s string, chars string) => bool`: reports whether any Unicode + code points in chars are within s. +- `count(s string, substr string) => int`: counts the number of non-overlapping + instances of substr in s. +- `equal_fold(s string, t string) => bool`: reports whether s and t, + interpreted as UTF-8 strings, +- `fields(s string) => [string]`: splits the string s around each instance of + one or more consecutive white space characters, as defined by unicode.IsSpace, + returning a slice of substrings of s or an empty slice if s contains only + white space. +- `has_prefix(s string, prefix string) => bool`: tests whether the string s + begins with prefix. +- `has_suffix(s string, suffix string) => bool`: tests whether the string s + ends with suffix. +- `index(s string, substr string) => int`: returns the index of the first + instance of substr in s, or -1 if substr is not present in s. +- `index_any(s string, chars string) => int`: returns the index of the first + instance of any Unicode code point from chars in s, or -1 if no Unicode code + point from chars is present in s. +- `join(arr string, sep string) => string`: concatenates the elements of a to + create a single string. The separator string sep is placed between elements + in the resulting string. +- `last_index(s string, substr string) => int`: returns the index of the last + instance of substr in s, or -1 if substr is not present in s. +- `last_index_any(s string, chars string) => int`: returns the index of the + last instance of any Unicode code point from chars in s, or -1 if no Unicode + code point from chars is present in s. +- `repeat(s string, count int) => string`: returns a new string consisting of + count copies of the string s. +- `replace(s string, old string, new string, n int) => string`: returns a copy + of the string s with the first n non-overlapping instances of old replaced by + new. +- `substr(s string, lower int, upper int) => string => string`: returns a + substring of the string s specified by the lower and upper parameters. +- `split(s string, sep string) => [string]`: slices s into all substrings + separated by sep and returns a slice of the substrings between those + separators. +- `split_after(s string, sep string) => [string]`: slices s into all substrings + after each instance of sep and returns a slice of those substrings. +- `split_after_n(s string, sep string, n int) => [string]`: slices s into + substrings after each instance of sep and returns a slice of those substrings. +- `split_n(s string, sep string, n int) => [string]`: slices s into substrings + separated by sep and returns a slice of the substrings between those + separators. +- `title(s string) => string`: returns a copy of the string s with all Unicode + letters that begin words mapped to their title case. +- `to_lower(s string) => string`: returns a copy of the string s with all + Unicode letters mapped to their lower case. +- `to_title(s string) => string`: returns a copy of the string s with all + Unicode letters mapped to their title case. +- `to_upper(s string) => string`: returns a copy of the string s with all + Unicode letters mapped to their upper case. +- `pad_left(s string, pad_len int, pad_with string) => string`: returns a copy + of the string s padded on the left with the contents of the string pad_with + to length pad_len. If pad_with is not specified, white space is used as the + default padding. +- `pad_right(s string, pad_len int, pad_with string) => string`: returns a + copy of the string s padded on the right with the contents of the string + pad_with to length pad_len. If pad_with is not specified, white space is + used as the default padding. +- `trim(s string, cutset string) => string`: returns a slice of the string s + with all leading and trailing Unicode code points contained in cutset removed. +- `trim_left(s string, cutset string) => string`: returns a slice of the string + s with all leading Unicode code points contained in cutset removed. +- `trim_prefix(s string, prefix string) => string`: returns s without the + provided leading prefix string. +- `trim_right(s string, cutset string) => string`: returns a slice of the + string s, with all trailing Unicode code points contained in cutset removed. +- `trim_space(s string) => string`: returns a slice of the string s, with all + leading and trailing white space removed, as defined by Unicode. +- `trim_suffix(s string, suffix string) => string`: returns s without the + provided trailing suffix string. +- `atoi(str string) => int/error`: returns the result of ParseInt(s, 10, 0) + converted to type int. +- `format_bool(b bool) => string`: returns "true" or "false" according to the + value of b. +- `format_float(f float, fmt string, prec int, bits int) => string`: converts + the floating-point number f to a string, according to the format fmt and + precision prec. +- `format_int(i int, base int) => string`: returns the string representation of + i in the given base, for 2 <= base <= 36. The result uses the lower-case + letters 'a' to 'z' for digit values >= 10. - `itoa(i int) => string`: is shorthand for format_int(i, 10). -- `parse_bool(s string) => bool/error`: returns the boolean value represented by the string. It accepts 1, t, T, TRUE, true, True, 0, f, F, FALSE, false, False. Any other value returns an error. -- `parse_float(s string, bits int) => float/error`: converts the string s to a floating-point number with the precision specified by bitSize: 32 for float32, or 64 for float64. When bitSize=32, the result still has type float64, but it will be convertible to float32 without changing its value. -- `parse_int(s string, base int, bits int) => int/error`: interprets a string s in the given base (0, 2 to 36) and bit size (0 to 64) and returns the corresponding value i. -- `quote(s string) => string`: returns a double-quoted Go string literal representing s. The returned string uses Go escape sequences (\t, \n, \xFF, \u0100) for control characters and non-printable characters as defined by IsPrint. -- `unquote(s string) => string/error`: interprets s as a single-quoted, double-quoted, or backquoted Go string literal, returning the string value that s quotes. (If s is single-quoted, it would be a Go character literal; Unquote returns the corresponding one-character string.) +- `parse_bool(s string) => bool/error`: returns the boolean value represented + by the string. It accepts 1, t, T, TRUE, true, True, 0, f, F, FALSE, false, + False. Any other value returns an error. +- `parse_float(s string, bits int) => float/error`: converts the string s to a + floating-point number with the precision specified by bitSize: 32 for float32, + or 64 for float64. When bitSize=32, the result still has type float64, but it + will be convertible to float32 without changing its value. +- `parse_int(s string, base int, bits int) => int/error`: interprets a string s + in the given base (0, 2 to 36) and bit size (0 to 64) and returns the + corresponding value i. +- `quote(s string) => string`: returns a double-quoted Go string literal + representing s. The returned string uses Go escape sequences (\t, \n, \xFF, + \u0100) for control characters and non-printable characters as defined by + IsPrint. +- `unquote(s string) => string/error`: interprets s as a single-quoted, + double-quoted, or backquoted Go string literal, returning the string value + that s quotes. (If s is single-quoted, it would be a Go character literal; + Unquote returns the corresponding one-character string.) ## Regexp -- `match(text string) => bool`: reports whether the string s contains any match of the regular expression pattern. -- `find(text string, count int) => [[{text: string, begin: int, end: int}]]/undefined`: returns an array holding all matches, each of which is an array of map object that contains matching text, begin and end (exclusive) index. -- `replace(src string, repl string) => string`: returns a copy of src, replacing matches of the pattern with the replacement string repl. -- `split(text string, count int) => [string]`: slices s into substrings separated by the expression and returns a slice of the substrings between those expression matches. +- `match(text string) => bool`: reports whether the string s contains any match + of the regular expression pattern. +- `find(text string, count int) => [[{text: string, begin: int, end: int}]]/undefined`: + returns an array holding all matches, each of which is an array of map object + that contains matching text, begin and end (exclusive) index. +- `replace(src string, repl string) => string`: returns a copy of src, + replacing matches of the pattern with the replacement string repl. +- `split(text string, count int) => [string]`: slices s into substrings + separated by the expression and returns a slice of the substrings between + those expression matches. diff --git a/docs/stdlib-times.md b/docs/stdlib-times.md index bd03ecc..7ffacf6 100644 --- a/docs/stdlib-times.md +++ b/docs/stdlib-times.md @@ -42,38 +42,79 @@ times := import("times") ## Functions -- `sleep(duration int)`: pauses the current goroutine for at least the duration d. A negative or zero duration causes Sleep to return immediately. -- `parse_duration(s string) => int`: parses a duration string. A duration string is a possibly signed sequence of decimal numbers, each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m". Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". +- `sleep(duration int)`: pauses the current goroutine for at least the duration + d. A negative or zero duration causes Sleep to return immediately. +- `parse_duration(s string) => int`: parses a duration string. A duration + string is a possibly signed sequence of decimal numbers, each with optional + fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m". Valid time + units are "ns", "us" (or "µs"), "ms", "s", "m", "h". - `since(t time) => int`: returns the time elapsed since t. - `until(t time) => int`: returns the duration until t. -- `duration_hours(duration int) => float`: returns the duration as a floating point number of hours. -- `duration_minutes(duration int) => float`: returns the duration as a floating point number of minutes. -- `duration_nanoseconds(duration int) => int`: returns the duration as an integer of nanoseconds. -- `duration_seconds(duration int) => float`: returns the duration as a floating point number of seconds. -- `duration_string(duration int) => string`: returns a string representation of duration. -- `month_string(month int) => string`: returns the English name of the month ("January", "February", ...). -- `date(year int, month int, day int, hour int, min int, sec int, nsec int) => time`: returns the Time corresponding to "yyyy-mm-dd hh:mm:ss + nsec nanoseconds". Current location is used. +- `duration_hours(duration int) => float`: returns the duration as a floating + point number of hours. +- `duration_minutes(duration int) => float`: returns the duration as a floating + point number of minutes. +- `duration_nanoseconds(duration int) => int`: returns the duration as an + integer of nanoseconds. +- `duration_seconds(duration int) => float`: returns the duration as a floating + point number of seconds. +- `duration_string(duration int) => string`: returns a string representation of + duration. +- `month_string(month int) => string`: returns the English name of the month + ("January", "February", ...). +- `date(year int, month int, day int, hour int, min int, sec int, nsec int) => time`: + returns the Time corresponding to "yyyy-mm-dd hh:mm:ss + nsec nanoseconds". + Current location is used. - `now() => time`: returns the current local time. -- `parse(format string, s string) => time`: parses a formatted string and returns the time value it represents. The layout defines the format by showing how the reference time, defined to be "Mon Jan 2 15:04:05 -0700 MST 2006" would be interpreted if it were the value; it serves as an example of the input format. The same interpretation will then be made to the input string. -- `unix(sec int, nsec int) => time`: returns the local Time corresponding to the given Unix time, sec seconds and nsec nanoseconds since January 1, 1970 UTC. +- `parse(format string, s string) => time`: parses a formatted string and + returns the time value it represents. The layout defines the format by + showing how the reference time, defined to be "Mon Jan 2 15:04:05 -0700 MST + 2006" would be interpreted if it were the value; it serves as an example of + the input format. The same interpretation will then be made to the input + string. +- `unix(sec int, nsec int) => time`: returns the local Time corresponding to + the given Unix time, sec seconds and nsec nanoseconds since January 1, + 1970 UTC. - `add(t time, duration int) => time`: returns the time t+d. -- `add_date(t time, years int, months int, days int) => time`: returns the time corresponding to adding the given number of years, months, and days to t. For example, AddDate(-1, 2, 3) applied to January 1, 2011 returns March 4, 2010. +- `add_date(t time, years int, months int, days int) => time`: returns the time + corresponding to adding the given number of years, months, and days to t. For + example, AddDate(-1, 2, 3) applied to January 1, 2011 returns March 4, 2010. - `sub(t time, u time) => int`: returns the duration t-u. -- `after(t time, u time) => bool`: reports whether the time instant t is after u. -- `before(t time, u time) => bool`: reports whether the time instant t is before u. +- `after(t time, u time) => bool`: reports whether the time instant t is after + u. +- `before(t time, u time) => bool`: reports whether the time instant t is + before u. - `time_year(t time) => int`: returns the year in which t occurs. - `time_month(t time) => int`: returns the month of the year specified by t. - `time_day(t time) => int`: returns the day of the month specified by t. - `time_weekday(t time) => int`: returns the day of the week specified by t. -- `time_hour(t time) => int`: returns the hour within the day specified by t, in the range [0, 23]. -- `time_minute(t time) => int`: returns the minute offset within the hour specified by t, in the range [0, 59]. -- `time_second(t time) => int`: returns the second offset within the minute specified by t, in the range [0, 59]. -- `time_nanosecond(t time) => int`: returns the nanosecond offset within the second specified by t, in the range [0, 999999999]. -- `time_unix(t time) => int`: returns t as a Unix time, the number of seconds elapsed since January 1, 1970 UTC. The result does not depend on the location associated with t. -- `time_unix_nano(t time) => int`: returns t as a Unix time, the number of nanoseconds elapsed since January 1, 1970 UTC. The result is undefined if the Unix time in nanoseconds cannot be represented by an int64 (a date before the year 1678 or after 2262). Note that this means the result of calling UnixNano on the zero Time is undefined. The result does not depend on the location associated with t. -- `time_format(t time, format) => string`: returns a textual representation of the time value formatted according to layout, which defines the format by showing how the reference time, defined to be "Mon Jan 2 15:04:05 -0700 MST 2006" would be displayed if it were the value; it serves as an example of the desired output. The same display rules will then be applied to the time value. -- `time_location(t time) => string`: returns the time zone name associated with t. -- `time_string(t time) => string`: returns the time formatted using the format string "2006-01-02 15:04:05.999999999 -0700 MST". -- `is_zero(t time) => bool`: reports whether t represents the zero time instant, January 1, year 1, 00:00:00 UTC. +- `time_hour(t time) => int`: returns the hour within the day specified by t, + in the range [0, 23]. +- `time_minute(t time) => int`: returns the minute offset within the hour + specified by t, in the range [0, 59]. +- `time_second(t time) => int`: returns the second offset within the minute + specified by t, in the range [0, 59]. +- `time_nanosecond(t time) => int`: returns the nanosecond offset within the + second specified by t, in the range [0, 999999999]. +- `time_unix(t time) => int`: returns t as a Unix time, the number of seconds + elapsed since January 1, 1970 UTC. The result does not depend on the location + associated with t. +- `time_unix_nano(t time) => int`: returns t as a Unix time, the number of + nanoseconds elapsed since January 1, 1970 UTC. The result is undefined if the + Unix time in nanoseconds cannot be represented by an int64 (a date before the + year 1678 or after 2262). Note that this means the result of calling UnixNano + on the zero Time is undefined. The result does not depend on the location + associated with t. +- `time_format(t time, format) => string`: returns a textual representation of + he time value formatted according to layout, which defines the format by + showing how the reference time, defined to be "Mon Jan 2 15:04:05 -0700 MST + 2006" would be displayed if it were the value; it serves as an example of the + desired output. The same display rules will then be applied to the time value. +- `time_location(t time) => string`: returns the time zone name associated with + t. +- `time_string(t time) => string`: returns the time formatted using the format + string "2006-01-02 15:04:05.999999999 -0700 MST". +- `is_zero(t time) => bool`: reports whether t represents the zero time + instant, January 1, year 1, 00:00:00 UTC. - `to_local(t time) => time`: returns t with the location set to local time. - `to_utc(t time) => time`: returns t with the location set to UTC. \ No newline at end of file diff --git a/docs/stdlib.md b/docs/stdlib.md index 6383060..5890d8c 100644 --- a/docs/stdlib.md +++ b/docs/stdlib.md @@ -1,12 +1,22 @@ # Standard Library -- [os](https://github.com/d5/tengo/blob/master/docs/stdlib-os.md): platform-independent interface to operating system functionality. -- [text](https://github.com/d5/tengo/blob/master/docs/stdlib-text.md): regular expressions, string conversion, and manipulation -- [math](https://github.com/d5/tengo/blob/master/docs/stdlib-math.md): mathematical constants and functions -- [times](https://github.com/d5/tengo/blob/master/docs/stdlib-times.md): time-related functions -- [rand](https://github.com/d5/tengo/blob/master/docs/stdlib-rand.md): random functions -- [fmt](https://github.com/d5/tengo/blob/master/docs/stdlib-fmt.md): formatting functions -- [json](https://github.com/d5/tengo/blob/master/docs/stdlib-json.md): JSON functions -- [enum](https://github.com/d5/tengo/blob/master/docs/stdlib-enum.md): Enumeration functions -- [hex](https://github.com/d5/tengo/blob/master/docs/stdlib-hex.md): hex encoding and decoding functions -- [base64](https://github.com/d5/tengo/blob/master/docs/stdlib-base64.md): base64 encoding and decoding functions \ No newline at end of file +- [os](https://github.com/d5/tengo/blob/master/docs/stdlib-os.md): + platform-independent interface to operating system functionality. +- [text](https://github.com/d5/tengo/blob/master/docs/stdlib-text.md): regular + expressions, string conversion, and manipulation +- [math](https://github.com/d5/tengo/blob/master/docs/stdlib-math.md): + mathematical constants and functions +- [times](https://github.com/d5/tengo/blob/master/docs/stdlib-times.md): + time-related functions +- [rand](https://github.com/d5/tengo/blob/master/docs/stdlib-rand.md): + random functions +- [fmt](https://github.com/d5/tengo/blob/master/docs/stdlib-fmt.md): + formatting functions +- [json](https://github.com/d5/tengo/blob/master/docs/stdlib-json.md): JSON + functions +- [enum](https://github.com/d5/tengo/blob/master/docs/stdlib-enum.md): + Enumeration functions +- [hex](https://github.com/d5/tengo/blob/master/docs/stdlib-hex.md): hex + encoding and decoding functions +- [base64](https://github.com/d5/tengo/blob/master/docs/stdlib-base64.md): + base64 encoding and decoding functions \ No newline at end of file diff --git a/docs/tengo-cli.md b/docs/tengo-cli.md index 2780898..76f5771 100644 --- a/docs/tengo-cli.md +++ b/docs/tengo-cli.md @@ -1,6 +1,7 @@ # Tengo CLI Tool -Tengo is designed as an embedding script language for Go, but, it can also be compiled and executed as native binary using `tengo` CLI tool. +Tengo is designed as an embedding script language for Go, but, it can also be +compiled and executed as native binary using `tengo` CLI tool. ## Installing Tengo CLI @@ -10,11 +11,13 @@ To install `tengo` tool, run: go get github.com/d5/tengo/cmd/tengo ``` -Or, you can download the precompiled binaries from [here](https://github.com/d5/tengo/releases/latest). +Or, you can download the precompiled binaries from +[here](https://github.com/d5/tengo/releases/latest). ## Compiling and Executing Tengo Code -You can directly execute the Tengo source code by running `tengo` tool with your Tengo source file (`*.tengo`). +You can directly execute the Tengo source code by running `tengo` tool with +your Tengo source file (`*.tengo`). ```bash tengo myapp.tengo @@ -29,7 +32,8 @@ tengo myapp # execute the compiled binary `myapp` ## Tengo REPL -You can run Tengo [REPL](https://en.wikipedia.org/wiki/Read–eval–print_loop) if you run `tengo` with no arguments. +You can run Tengo [REPL](https://en.wikipedia.org/wiki/Read–eval–print_loop) +if you run `tengo` with no arguments. ```bash tengo diff --git a/docs/tutorial.md b/docs/tutorial.md index 701bdb6..1a2f666 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -1,6 +1,7 @@ # Tengo Language Syntax -Tengo's syntax is designed to be familiar to Go developers while being a bit simpler and more streamlined. +Tengo's syntax is designed to be familiar to Go developers while being a bit +simpler and more streamlined. **You can test the Tengo code in online [Playground](https://tengolang.com).** @@ -41,7 +42,10 @@ Here's a list of all available value types in Tengo. #### Error Values -In Tengo, an error can be represented using "error" typed values. An error value is created using `error` expression, and, it must have an underlying value. The underlying value of an error value can be access using `.value` selector. +In Tengo, an error can be represented using "error" typed values. An error +value is created using `error` expression, and, it must have an underlying +value. The underlying value of an error value can be access using `.value` +selector. ```golang err1 := error("oops") // error with string value @@ -70,7 +74,8 @@ b := immutable([1, 2, 3]) b[1] = "foo" // illegal: 'b' references to an immutable array. ``` -Note that re-assigning a new value to the variable has nothing to do with the value immutability. +Note that re-assigning a new value to the variable has nothing to do with the +value immutability. ```golang s := "abc" @@ -79,7 +84,10 @@ a := immutable([1, 2, 3]) a = false // ok ``` -Note that, if you copy (using `copy` builtin function) an immutable value, it will return a "mutable" copy. Also, immutability is not applied to the individual elements of the array or map value, unless they are explicitly made immutable. +Note that, if you copy (using `copy` builtin function) an immutable value, it +will return a "mutable" copy. Also, immutability is not applied to the +individual elements of the array or map value, unless they are explicitly made +immutable. ```golang a := immutable({b: 4, c: [1, 2, 3]}) @@ -92,11 +100,15 @@ a.c[1] = 5 // illegal #### Undefined Values -In Tengo, an "undefined" value can be used to represent an unexpected or non-existing value: +In Tengo, an "undefined" value can be used to represent an unexpected or +non-existing value: -- A function that does not return a value explicitly considered to return `undefined` value. -- Indexer or selector on composite value types may return `undefined` if the key or index does not exist. -- Type conversion builtin functions without a default value will return `undefined` if conversion fails. +- A function that does not return a value explicitly considered to return +`undefined` value. +- Indexer or selector on composite value types may return `undefined` if the +key or index does not exist. +- Type conversion builtin functions without a default value will return +`undefined` if conversion fails. ```golang a := func() { b := 4 }() // a == undefined @@ -107,7 +119,8 @@ d := int("foo") // d == undefined #### Array Values -In Tengo, array is an ordered list of values of any types. Elements of an array can be accessed using indexer `[]`. +In Tengo, array is an ordered list of values of any types. Elements of an array +can be accessed using indexer `[]`. ```golang [1, 2, 3][0] // == 1 @@ -119,7 +132,9 @@ In Tengo, array is an ordered list of values of any types. Elements of an array #### Map Values -In Tengo, map is a set of key-value pairs where key is string and the value is of any value types. Value of a map can be accessed using indexer `[]` or selector '.' operators. +In Tengo, map is a set of key-value pairs where key is string and the value is +of any value types. Value of a map can be accessed using indexer `[]` or +selector '.' operators. ```golang m := { a: 1, b: false, c: "foo" } @@ -132,7 +147,9 @@ m.x // == undefined #### Function Values -In Tengo, function is a callable value with a number of function arguments and a return value. Just like any other values, functions can be passed into or returned from another function. +In Tengo, function is a callable value with a number of function arguments and +a return value. Just like any other values, functions can be passed into or +returned from another function. ```golang my_func := func(arg1, arg2) { @@ -184,7 +201,8 @@ A value can be assigned to a variable using assignment operator `:=` and `=`. - `:=` operator defines a new variable in the scope and assigns a value. - `=` operator assigns a new value to an existing variable in the scope. -Variables are defined either in global scope (defined outside function) or in local scope (defined inside function). +Variables are defined either in global scope (defined outside function) or in +local scope (defined inside function). ```golang a := "foo" // define 'a' in global scope @@ -225,7 +243,10 @@ a = [1, 2, 3] // re-assigned 'array' ## Type Conversions -Although the type is not directly specified in Tengo, one can use type conversion [builtin functions](https://github.com/d5/tengo/blob/master/docs/builtins.md) to convert between value types. +Although the type is not directly specified in Tengo, one can use type +conversion +[builtin functions](https://github.com/d5/tengo/blob/master/docs/builtins.md) +to convert between value types. ```golang s1 := string(1984) // "1984" @@ -235,7 +256,8 @@ b4 := bool(1) // true c5 := char("X") // 'X' ``` -See [Operators](https://github.com/d5/tengo/blob/master/docs/operators.md) for more details on type coercions. +See [Operators](https://github.com/d5/tengo/blob/master/docs/operators.md) +for more details on type coercions. ## Operators @@ -248,7 +270,8 @@ See [Operators](https://github.com/d5/tengo/blob/master/docs/operators.md) for m | `!` | logical NOT | all types* | | `^` | bitwise complement | int | -_In Tengo, all values can be either [truthy or falsy](https://github.com/d5/tengo/blob/d5-patch-1/docs/runtime-types.md#objectisfalsy)._ +_In Tengo, all values can be either +[truthy or falsy](https://github.com/d5/tengo/blob/d5-patch-1/docs/runtime-types.md#objectisfalsy)._ ### Binary Operators @@ -273,7 +296,8 @@ _In Tengo, all values can be either [truthy or falsy](https://github.com/d5/teng | `>` | greater than | int, float, char, time | | `>=` | greater than or equal to | int, float, char, time | -_See [Operators](https://github.com/d5/tengo/blob/d5-patch-1/docs/operators.md) for more details._ +_See [Operators](https://github.com/d5/tengo/blob/d5-patch-1/docs/operators.md) +for more details._ ### Ternary Operators @@ -308,7 +332,10 @@ b := min(5, 10) // b == 5 ### Operator Precedences -Unary operators have the highest precedence, and, ternary operator has the lowest precendece. There are five precedence levels for binary operators. Multiplication operators bind strongest, followed by addition operators, comparison operators, `&&` (logical AND), and finally `||` (logical OR): +Unary operators have the highest precedence, and, ternary operator has the +lowest precedence. There are five precedence levels for binary operators. +Multiplication operators bind strongest, followed by addition operators, +comparison operators, `&&` (logical AND), and finally `||` (logical OR): | Precedence | Operator | | :---: | :---: | @@ -318,11 +345,13 @@ Unary operators have the highest precedence, and, ternary operator has the lowes | 2 | `&&` | | 1 | `\|\|` | -Like Go, `++` and `--` operators form statements, not expressions, they fall outside the operator hierarchy. +Like Go, `++` and `--` operators form statements, not expressions, they fall +outside the operator hierarchy. ### Selector and Indexer -One can use selector (`.`) and indexer (`[]`) operators to read or write elements of composite types (array, map, string, bytes). +One can use selector (`.`) and indexer (`[]`) operators to read or write +elements of composite types (array, map, string, bytes). ```golang ["one", "two", "three"][1] // == "two" @@ -342,7 +371,8 @@ m.b[5] = 0 // == undefined m.x.y.z // == undefined ``` -Like Go, one can use slice operator `[:]` for sequence value types such as array, string, bytes. +Like Go, one can use slice operator `[:]` for sequence value types such as +array, string, bytes. ```golang a := [1, 2, 3, 4, 5][1:3] // == [2, 3] @@ -369,7 +399,8 @@ if a < 0 { } ``` -Like Go, the condition expression may be preceded by a simple statement, which executes before the expression is evaluated. +Like Go, the condition expression may be preceded by a simple statement, +which executes before the expression is evaluated. ```golang if a := foo(); a < 0 { @@ -400,7 +431,9 @@ for { ### For-In Statement -"For-In" statement is new in Tengo. It's similar to Go's `for range` statement. "For-In" statement can iterate any iterable value types (array, map, bytes, string, undefined). +"For-In" statement is new in Tengo. It's similar to Go's `for range` statement. +"For-In" statement can iterate any iterable value types (array, map, bytes, +string, undefined). ```golang for v in [1, 2, 3] { // array: element @@ -418,7 +451,8 @@ for k, v in {k1: 1, k2: 2} { // map: key and value ## Modules -Module is the basic compilation unit in Tengo. A module can import another module using `import` expression. +Module is the basic compilation unit in Tengo. A module can import another +module using `import` expression. Main module: @@ -442,12 +476,17 @@ In Tengo, modules are very similar to functions. - `import` expression loads the module code and execute it like a function. - Module should return a value using `export` statement. - Module can return `export` a value of any types: int, map, function, etc. - - `export` in a module is like `return` in a function: it stops execution and return a value to the importing code. + - `export` in a module is like `return` in a function: it stops execution and + return a value to the importing code. - `export`-ed values are always immutable. - - If the module does not have any `export` statement, `import` expression simply returns `undefined`. _(Just like the function that has no `return`.)_ - - Note that `export` statement is completely ignored and not evaluated if the code is executed as a main module. + - If the module does not have any `export` statement, `import` expression + simply returns `undefined`. _(Just like the function that has no `return`.)_ + - Note that `export` statement is completely ignored and not evaluated if + the code is executed as a main module. -Also, you can use `import` expression to load the [Standard Library](https://github.com/d5/tengo/blob/master/docs/stdlib.md) as well. +Also, you can use `import` expression to load the +[Standard Library](https://github.com/d5/tengo/blob/master/docs/stdlib.md) as +well. ```golang math := import("math") @@ -456,7 +495,8 @@ a := math.abs(-19.84) // == 19.84 ## Comments -Like Go, Tengo supports line comments (`//...`) and block comments (`/* ... */`). +Like Go, Tengo supports line comments (`//...`) and block comments +(`/* ... */`). ```golang /* diff --git a/errors.go b/errors.go new file mode 100644 index 0000000..a3fd1f3 --- /dev/null +++ b/errors.go @@ -0,0 +1,64 @@ +package tengo + +import ( + "errors" + "fmt" +) + +var ( + // ErrStackOverflow is a stack overflow error. + ErrStackOverflow = errors.New("stack overflow") + + // ErrObjectAllocLimit is an objects allocation limit error. + ErrObjectAllocLimit = errors.New("object allocation limit exceeded") + + // ErrIndexOutOfBounds is an error where a given index is out of the + // bounds. + ErrIndexOutOfBounds = errors.New("index out of bounds") + + // ErrInvalidIndexType represents an invalid index type. + ErrInvalidIndexType = errors.New("invalid index type") + + // ErrInvalidIndexValueType represents an invalid index value type. + ErrInvalidIndexValueType = errors.New("invalid index value type") + + // ErrInvalidIndexOnError represents an invalid index on error. + ErrInvalidIndexOnError = errors.New("invalid index on error") + + // ErrInvalidOperator represents an error for invalid operator usage. + ErrInvalidOperator = errors.New("invalid operator") + + // ErrWrongNumArguments represents a wrong number of arguments error. + ErrWrongNumArguments = errors.New("wrong number of arguments") + + // ErrBytesLimit represents an error where the size of bytes value exceeds + // the limit. + ErrBytesLimit = errors.New("exceeding bytes size limit") + + // ErrStringLimit represents an error where the size of string value + // exceeds the limit. + ErrStringLimit = errors.New("exceeding string size limit") + + // ErrNotIndexable is an error where an Object is not indexable. + ErrNotIndexable = errors.New("not indexable") + + // ErrNotIndexAssignable is an error where an Object is not index + // assignable. + ErrNotIndexAssignable = errors.New("not index-assignable") + + // ErrNotImplemented is an error where an Object has not implemented a + // required method. + ErrNotImplemented = errors.New("not implemented") +) + +// ErrInvalidArgumentType represents an invalid argument value type error. +type ErrInvalidArgumentType struct { + Name string + Expected string + Found string +} + +func (e ErrInvalidArgumentType) Error() string { + return fmt.Sprintf("invalid type for argument '%s': expected %s, found %s", + e.Name, e.Expected, e.Found) +} diff --git a/objects/formatter.go b/formatter.go similarity index 91% rename from objects/formatter.go rename to formatter.go index 95d7f6b..0dbf71c 100644 --- a/objects/formatter.go +++ b/formatter.go @@ -1,15 +1,13 @@ -package objects +package tengo import ( "strconv" "sync" "unicode/utf8" - - "github.com/d5/tengo" ) -// Strings for use with buffer.WriteString. -// This is less overhead than using buffer.Write with byte arrays. +// Strings for use with fmtbuf.WriteString. This is less overhead than using +// fmtbuf.Write with byte arrays. const ( commaSpaceString = ", " nilParenString = "(nil)" @@ -55,9 +53,9 @@ type fmtFlags struct { } // A formatter is the raw formatter used by Printf etc. -// It prints into a buffer that must be set up separately. +// It prints into a fmtbuf that must be set up separately. type formatter struct { - buf *buffer + buf *fmtbuf fmtFlags @@ -69,13 +67,13 @@ type formatter struct { intbuf [68]byte } -func (f *formatter) clearflags() { +func (f *formatter) clearFlags() { f.fmtFlags = fmtFlags{} } -func (f *formatter) init(buf *buffer) { +func (f *formatter) init(buf *fmtbuf) { f.buf = buf - f.clearflags() + f.clearFlags() } // writePadding generates n bytes of padding. @@ -87,13 +85,13 @@ func (f *formatter) writePadding(n int) { oldLen := len(buf) newLen := oldLen + n - if newLen > tengo.MaxStringLen { + if newLen > MaxStringLen { panic(ErrStringLimit) } // Make enough room for padding. if newLen > cap(buf) { - buf = make(buffer, cap(buf)*2+n) + buf = make(fmtbuf, cap(buf)*2+n) copy(buf, *f.buf) } // Decide which byte the padding should be filled with. @@ -171,10 +169,12 @@ func (f *formatter) fmtUnicode(u uint64) { } } - // Format into buf, ending at buf[i]. Formatting numbers is easier right-to-left. + // Format into buf, ending at buf[i]. Formatting numbers is easier + // right-to-left. i := len(buf) - // For %#U we want to add a space and a quoted character at the end of the buffer. + // For %#U we want to add a space and a quoted character at the end of + // the fmtbuf. if f.sharp && u <= utf8.MaxRune && strconv.IsPrint(rune(u)) { i-- buf[i] = '\'' @@ -214,7 +214,13 @@ func (f *formatter) fmtUnicode(u uint64) { } // fmtInteger formats signed and unsigned integers. -func (f *formatter) fmtInteger(u uint64, base int, isSigned bool, verb rune, digits string) { +func (f *formatter) fmtInteger( + u uint64, + base int, + isSigned bool, + verb rune, + digits string, +) { negative := isSigned && int64(u) < 0 if negative { u = -u @@ -253,9 +259,10 @@ func (f *formatter) fmtInteger(u uint64, base int, isSigned bool, verb rune, dig } } - // Because printing is easier right-to-left: format u into buf, ending at buf[i]. - // We could make things marginally faster by splitting the 32-bit case out - // into a separate block but it's not worth the duplication, so u has 64 bits. + // Because printing is easier right-to-left: format u into buf, ending at + // buf[i]. We could make things marginally faster by splitting the 32-bit + // case out into a separate block but it's not worth the duplication, so + // u has 64 bits. i := len(buf) // Use constants for the division and modulo for more efficient code. // Switch cases ordered by popularity. @@ -357,7 +364,8 @@ func (f *formatter) truncateString(s string) string { return s } -// truncate truncates the byte slice b as a string of the specified precision, if present. +// truncate truncates the byte slice b as a string of the specified precision, +// if present. func (f *formatter) truncate(b []byte) []byte { if f.precPresent { n := f.prec @@ -399,11 +407,13 @@ func (f *formatter) fmtSbx(s string, b []byte, digits string) { if f.precPresent && f.prec < length { length = f.prec } - // Compute width of the encoding taking into account the f.sharp and f.space flag. + // Compute width of the encoding taking into account the f.sharp and + // f.space flag. width := 2 * length if width > 0 { if f.space { - // Each element encoded by two hexadecimals will get a leading 0x or 0X. + // Each element encoded by two hexadecimals will get a leading + // 0x or 0X. if f.sharp { width *= 2 } @@ -423,7 +433,7 @@ func (f *formatter) fmtSbx(s string, b []byte, digits string) { if f.widPresent && f.wid > width && !f.minus { f.writePadding(f.wid - width) } - // Write the encoding directly into the output buffer. + // Write the encoding directly into the output fmtbuf. buf := *f.buf if f.sharp { // Add leading 0x or 0X. @@ -589,8 +599,9 @@ func (f *formatter) fmtFloat(v float64, size int, verb rune, prec int) { } // We want a sign if asked for and if the sign is not positive. if f.plus || num[0] != '+' { - // If we're zero padding to the left we want the sign before the leading zeros. - // Achieve this by writing the sign out and then padding the unsigned number. + // If we're zero padding to the left we want the sign before the + // leading zeros. Achieve this by writing the sign out and then padding + // the unsigned number. if f.zero && f.widPresent && f.wid > len(num) { f.buf.WriteSingleByte(num[0]) f.writePadding(f.wid - len(num)) @@ -600,39 +611,40 @@ func (f *formatter) fmtFloat(v float64, size int, verb rune, prec int) { f.pad(num) return } - // No sign to show and the number is positive; just print the unsigned number. + // No sign to show and the number is positive; just print the unsigned + // number. f.pad(num[1:]) } // Use simple []byte instead of bytes.Buffer to avoid large dependency. -type buffer []byte +type fmtbuf []byte -func (b *buffer) Write(p []byte) { - if len(*b)+len(p) > tengo.MaxStringLen { +func (b *fmtbuf) Write(p []byte) { + if len(*b)+len(p) > MaxStringLen { panic(ErrStringLimit) } *b = append(*b, p...) } -func (b *buffer) WriteString(s string) { - if len(*b)+len(s) > tengo.MaxStringLen { +func (b *fmtbuf) WriteString(s string) { + if len(*b)+len(s) > MaxStringLen { panic(ErrStringLimit) } *b = append(*b, s...) } -func (b *buffer) WriteSingleByte(c byte) { - if len(*b) >= tengo.MaxStringLen { +func (b *fmtbuf) WriteSingleByte(c byte) { + if len(*b) >= MaxStringLen { panic(ErrStringLimit) } *b = append(*b, c) } -func (b *buffer) WriteRune(r rune) { - if len(*b)+utf8.RuneLen(r) > tengo.MaxStringLen { +func (b *fmtbuf) WriteRune(r rune) { + if len(*b)+utf8.RuneLen(r) > MaxStringLen { panic(ErrStringLimit) } @@ -650,9 +662,10 @@ func (b *buffer) WriteRune(r rune) { *b = b2[:n+w] } -// pp is used to store a printer's state and is reused with sync.Pool to avoid allocations. +// pp is used to store a printer's state and is reused with sync.Pool to avoid +// allocations. type pp struct { - buf buffer + buf fmtbuf // arg holds the current item. arg Object @@ -663,10 +676,12 @@ type pp struct { // reordered records whether the format string used argument reordering. reordered bool - // goodArgNum records whether the most recent reordering directive was valid. + // goodArgNum records whether the most recent reordering directive was + // valid. goodArgNum bool - // erroring is set when printing an error string to guard against calling handleMethods. + // erroring is set when printing an error string to guard against calling + // handleMethods. erroring bool } @@ -686,8 +701,8 @@ func newPrinter() *pp { func (p *pp) free() { // Proper usage of a sync.Pool requires each entry to have approximately // the same memory cost. To obtain this property when the stored type - // contains a variably-sized buffer, we add a hard limit on the maximum buffer - // to place back in the pool. + // contains a variably-sized fmtbuf, we add a hard limit on the maximum + // fmtbuf to place back in the pool. // // See https://golang.org/issue/23199 if cap(p.buf) > 64<<10 { @@ -699,9 +714,13 @@ func (p *pp) free() { ppFree.Put(p) } -func (p *pp) Width() (wid int, ok bool) { return p.fmt.wid, p.fmt.widPresent } +func (p *pp) Width() (wid int, ok bool) { + return p.fmt.wid, p.fmt.widPresent +} -func (p *pp) Precision() (prec int, ok bool) { return p.fmt.prec, p.fmt.precPresent } +func (p *pp) Precision() (prec int, ok bool) { + return p.fmt.prec, p.fmt.precPresent +} func (p *pp) Flag(b int) bool { switch b { @@ -750,7 +769,8 @@ func tooLarge(x int) bool { return x > max || x < -max } -// parsenum converts ASCII to integer. num is 0 (and isnum is false) if no number present. +// parsenum converts ASCII to integer. num is 0 (and isnum is false) if no +// number present. func parsenum(s string, start, end int) (num int, isnum bool, newi int) { if start >= end { return 0, false, end @@ -918,7 +938,8 @@ func (p *pp) printArg(arg Object, verb rune) { } // Special processing considerations. - // %T (the value's type) and %p (its address) are special; we always do them first. + // %T (the value's type) and %p (its address) are special; we always do + // them first. switch verb { case 'T': p.fmt.fmtS(arg.TypeName()) @@ -945,7 +966,8 @@ func (p *pp) printArg(arg Object, verb rune) { } } -// intFromArg gets the argNumth element of a. On return, isInt reports whether the argument has integer type. +// intFromArg gets the argNumth element of a. On return, isInt reports whether +// the argument has integer type. func intFromArg(a []Object, argNum int) (num int, isInt bool, newArgNum int) { newArgNum = argNum if argNum < len(a) { @@ -980,16 +1002,23 @@ func parseArgNumber(format string) (index int, wid int, ok bool) { if !ok || newi != i { return 0, i + 1, false } - return width - 1, i + 1, true // arg numbers are one-indexed and skip paren. + // arg numbers are one-indexed andskip paren. + return width - 1, i + 1, true } } return 0, 1, false } -// argNumber returns the next argument to evaluate, which is either the value of the passed-in -// argNum or the value of the bracketed integer that begins format[i:]. It also returns -// the new value of i, that is, the index of the next byte of the format to process. -func (p *pp) argNumber(argNum int, format string, i int, numArgs int) (newArgNum, newi int, found bool) { +// argNumber returns the next argument to evaluate, which is either the value +// of the passed-in argNum or the value of the bracketed integer that begins +// format[i:]. It also returns the new value of i, that is, the index of the +// next byte of the format to process. +func (p *pp) argNumber( + argNum int, + format string, + i int, + numArgs int, +) (newArgNum, newi int, found bool) { if len(format) <= i || format[i] != '[' { return argNum, i, false } @@ -1048,7 +1077,7 @@ formatLoop: i++ // Do we have flags? - p.fmt.clearflags() + p.fmt.clearFlags() simpleFormat: for ; i < end; i++ { c := format[i] @@ -1056,7 +1085,8 @@ formatLoop: case '#': p.fmt.sharp = true case '0': - p.fmt.zero = !p.fmt.minus // Only allow zero padding to the left. + // Only allow zero padding to the left. + p.fmt.zero = !p.fmt.minus case '+': p.fmt.plus = true case '-': @@ -1081,7 +1111,8 @@ formatLoop: i++ continue formatLoop } - // Format is more complex than simple flags and a verb or is malformed. + // Format is more complex than simple flags and a verb or is + // malformed. break simpleFormat } } @@ -1157,11 +1188,13 @@ formatLoop: i += size switch { - case verb == '%': // Percent does not absorb operands and ignores f.wid and f.prec. + case verb == '%': + // Percent does not absorb operands and ignores f.wid and f.prec. _, _ = p.WriteSingleByte('%') case !p.goodArgNum: p.badArgNum(verb) - case argNum >= len(a): // No argument left over to print for the current verb. + case argNum >= len(a): + // No argument left over to print for the current verb. p.missingArg(verb) case verb == 'v': // Go syntax @@ -1181,7 +1214,7 @@ formatLoop: // out of order, in which case it's too expensive to detect if they've all // been used and arguably OK if they're not. if !p.reordered && argNum < len(a) { - p.fmt.clearflags() + p.fmt.clearFlags() _, _ = p.WriteString(extraString) for i, arg := range a[argNum:] { if i > 0 { @@ -1201,7 +1234,7 @@ formatLoop: return nil } -// Format formats according to a format specifier and returns the resulting string. +// Format is like fmt.Sprintf but using Objects. func Format(format string, a ...Object) (string, error) { p := newPrinter() err := p.doFormat(format, a) diff --git a/go.mod b/go.mod index 8421e27..a8c5c11 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module github.com/d5/tengo -go 1.12 +go 1.13 diff --git a/go.sum b/go.sum index e69de29..56d89d3 100644 --- a/go.sum +++ b/go.sum @@ -0,0 +1,17 @@ +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f h1:J5lckAjkw6qYlOZNj90mLYNTEKDvWeuc1yieZ8qUzUE= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f h1:kDxGY2VmgABOe55qheT/TFqUMtcTHnomIPS1iv3G4Ms= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191213221258-04c2e8eff935 h1:kJQZhwFzSwJS2BxboKjdZzWczQOZx8VuH7Y8hhuGUtM= +golang.org/x/tools v0.0.0-20191213221258-04c2e8eff935/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/compiler/ast/ident_list.go b/internal/ast.go similarity index 65% rename from compiler/ast/ident_list.go rename to internal/ast.go index 8dd6d30..ff52b32 100644 --- a/compiler/ast/ident_list.go +++ b/internal/ast.go @@ -1,43 +1,51 @@ -package ast +package internal import ( "strings" - - "github.com/d5/tengo/compiler/source" ) +const ( + nullRep = "" +) + +// Node represents a node in the AST. +type Node interface { + // Pos returns the position of first character belonging to the node. + Pos() Pos + // End returns the position of first character immediately after the node. + End() Pos + // String returns a string representation of the node. + String() string +} + // IdentList represents a list of identifiers. type IdentList struct { - LParen source.Pos + LParen Pos VarArgs bool List []*Ident - RParen source.Pos + RParen Pos } // Pos returns the position of first character belonging to the node. -func (n *IdentList) Pos() source.Pos { +func (n *IdentList) Pos() Pos { if n.LParen.IsValid() { return n.LParen } - if len(n.List) > 0 { return n.List[0].Pos() } - - return source.NoPos + return NoPos } // End returns the position of first character immediately after the node. -func (n *IdentList) End() source.Pos { +func (n *IdentList) End() Pos { if n.RParen.IsValid() { return n.RParen + 1 } - if l := len(n.List); l > 0 { return n.List[l-1].End() } - - return source.NoPos + return NoPos } // NumFields returns the number of fields. @@ -45,7 +53,6 @@ func (n *IdentList) NumFields() int { if n == nil { return 0 } - return len(n.List) } @@ -58,6 +65,5 @@ func (n *IdentList) String() string { list = append(list, e.String()) } } - return "(" + strings.Join(list, ", ") + ")" } diff --git a/compiler/ast/ident_list_test.go b/internal/ast_test.go similarity index 52% rename from compiler/ast/ident_list_test.go rename to internal/ast_test.go index 871ae35..26ae063 100644 --- a/compiler/ast/ident_list_test.go +++ b/internal/ast_test.go @@ -1,14 +1,14 @@ -package ast_test +package internal_test import ( "testing" - "github.com/d5/tengo/compiler/ast" + "github.com/d5/tengo/internal" ) func TestIdentListString(t *testing.T) { - identListVar := &ast.IdentList{ - List: []*ast.Ident{ + identListVar := &internal.IdentList{ + List: []*internal.Ident{ {Name: "a"}, {Name: "b"}, {Name: "c"}, @@ -18,11 +18,12 @@ func TestIdentListString(t *testing.T) { expectedVar := "(a, b, ...c)" if str := identListVar.String(); str != expectedVar { - t.Fatalf("expected string of %#v to be %s, got %s", identListVar, expectedVar, str) + t.Fatalf("expected string of %#v to be %s, got %s", + identListVar, expectedVar, str) } - identList := &ast.IdentList{ - List: []*ast.Ident{ + identList := &internal.IdentList{ + List: []*internal.Ident{ {Name: "a"}, {Name: "b"}, {Name: "c"}, @@ -32,6 +33,7 @@ func TestIdentListString(t *testing.T) { expected := "(a, b, c)" if str := identList.String(); str != expected { - t.Fatalf("expected string of %#v to be %s, got %s", identList, expected, str) + t.Fatalf("expected string of %#v to be %s, got %s", + identList, expected, str) } } diff --git a/compiler/symbol_table.go b/internal/compile.go similarity index 70% rename from compiler/symbol_table.go rename to internal/compile.go index 94c868d..cf61e2d 100644 --- a/compiler/symbol_table.go +++ b/internal/compile.go @@ -1,4 +1,52 @@ -package compiler +package internal + +import "fmt" + +// CompilationScope represents a compiled instructions and the last two +// instructions that were emitted. +type CompilationScope struct { + Instructions []byte + SymbolInit map[string]bool + SourceMap map[int]Pos +} + +// Loop represents a loop construct that the compiler uses to track the current +// loop. +type Loop struct { + Continues []int + Breaks []int +} + +// CompilerError represents a compiler error. +type CompilerError struct { + FileSet *SourceFileSet + Node Node + Err error +} + +func (e *CompilerError) Error() string { + filePos := e.FileSet.Position(e.Node.Pos()) + return fmt.Sprintf("Compile Error: %s\n\tat %s", e.Err.Error(), filePos) +} + +// SymbolScope represents a symbol scope. +type SymbolScope string + +// List of symbol scopes +const ( + ScopeGlobal SymbolScope = "GLOBAL" + ScopeLocal SymbolScope = "LOCAL" + ScopeBuiltin SymbolScope = "BUILTIN" + ScopeFree SymbolScope = "FREE" +) + +// Symbol represents a symbol in the symbol table. +type Symbol struct { + Name string + Scope SymbolScope + Index int + LocalAssigned bool // if the local symbol is assigned at least once +} // SymbolTable represents a symbol table. type SymbolTable struct { @@ -28,11 +76,8 @@ func (t *SymbolTable) Define(name string) *Symbol { } else { symbol.Scope = ScopeLocal } - t.store[name] = symbol - t.updateMaxDefs(symbol.Index + 1) - return symbol } @@ -47,34 +92,32 @@ func (t *SymbolTable) DefineBuiltin(index int, name string) *Symbol { Index: index, Scope: ScopeBuiltin, } - t.store[name] = symbol - t.builtinSymbols = append(t.builtinSymbols, symbol) - return symbol } // Resolve resolves a symbol with a given name. -func (t *SymbolTable) Resolve(name string) (symbol *Symbol, depth int, ok bool) { +func (t *SymbolTable) Resolve( + name string, +) (symbol *Symbol, depth int, ok bool) { symbol, ok = t.store[name] if !ok && t.parent != nil { symbol, depth, ok = t.parent.Resolve(name) if !ok { return } - depth++ // if symbol is defined in parent table and if it's not global/builtin // then it's free variable. - if !t.block && depth > 0 && symbol.Scope != ScopeGlobal && symbol.Scope != ScopeBuiltin { + if !t.block && depth > 0 && + symbol.Scope != ScopeGlobal && + symbol.Scope != ScopeBuiltin { return t.defineFree(symbol), depth, true } - return } - return } @@ -92,7 +135,6 @@ func (t *SymbolTable) Parent(skipBlock bool) *SymbolTable { if skipBlock && t.block { return t.parent.Parent(skipBlock) } - return t.parent } @@ -111,7 +153,6 @@ func (t *SymbolTable) BuiltinSymbols() []*Symbol { if t.parent != nil { return t.parent.BuiltinSymbols() } - return t.builtinSymbols } @@ -128,7 +169,6 @@ func (t *SymbolTable) nextIndex() int { if t.block { return t.parent.nextIndex() + t.numDefinition } - return t.numDefinition } @@ -136,7 +176,6 @@ func (t *SymbolTable) updateMaxDefs(numDefs int) { if numDefs > t.maxDefinition { t.maxDefinition = numDefs } - if t.block { t.parent.updateMaxDefs(numDefs) } @@ -144,16 +183,12 @@ func (t *SymbolTable) updateMaxDefs(numDefs int) { func (t *SymbolTable) defineFree(original *Symbol) *Symbol { // TODO: should we check duplicates? - t.freeSymbols = append(t.freeSymbols, original) - symbol := &Symbol{ Name: original.Name, Index: len(t.freeSymbols) - 1, Scope: ScopeFree, } - t.store[original.Name] = symbol - return symbol } diff --git a/internal/expr.go b/internal/expr.go new file mode 100644 index 0000000..059b646 --- /dev/null +++ b/internal/expr.go @@ -0,0 +1,597 @@ +package internal + +import ( + "strings" + + "github.com/d5/tengo/internal/token" +) + +// Expr represents an expression node in the AST. +type Expr interface { + Node + exprNode() +} + +// ArrayLit represents an array literal. +type ArrayLit struct { + Elements []Expr + LBrack Pos + RBrack Pos +} + +func (e *ArrayLit) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *ArrayLit) Pos() Pos { + return e.LBrack +} + +// End returns the position of first character immediately after the node. +func (e *ArrayLit) End() Pos { + return e.RBrack + 1 +} + +func (e *ArrayLit) String() string { + var elements []string + for _, m := range e.Elements { + elements = append(elements, m.String()) + } + return "[" + strings.Join(elements, ", ") + "]" +} + +// BadExpr represents a bad expression. +type BadExpr struct { + From Pos + To Pos +} + +func (e *BadExpr) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *BadExpr) Pos() Pos { + return e.From +} + +// End returns the position of first character immediately after the node. +func (e *BadExpr) End() Pos { + return e.To +} + +func (e *BadExpr) String() string { + return "" +} + +// BinaryExpr represents a binary operator expression. +type BinaryExpr struct { + LHS Expr + RHS Expr + Token token.Token + TokenPos Pos +} + +func (e *BinaryExpr) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *BinaryExpr) Pos() Pos { + return e.LHS.Pos() +} + +// End returns the position of first character immediately after the node. +func (e *BinaryExpr) End() Pos { + return e.RHS.End() +} + +func (e *BinaryExpr) String() string { + return "(" + e.LHS.String() + " " + e.Token.String() + + " " + e.RHS.String() + ")" +} + +// BoolLit represents a boolean literal. +type BoolLit struct { + Value bool + ValuePos Pos + Literal string +} + +func (e *BoolLit) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *BoolLit) Pos() Pos { + return e.ValuePos +} + +// End returns the position of first character immediately after the node. +func (e *BoolLit) End() Pos { + return Pos(int(e.ValuePos) + len(e.Literal)) +} + +func (e *BoolLit) String() string { + return e.Literal +} + +// CallExpr represents a function call expression. +type CallExpr struct { + Func Expr + LParen Pos + Args []Expr + RParen Pos +} + +func (e *CallExpr) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *CallExpr) Pos() Pos { + return e.Func.Pos() +} + +// End returns the position of first character immediately after the node. +func (e *CallExpr) End() Pos { + return e.RParen + 1 +} + +func (e *CallExpr) String() string { + var args []string + for _, e := range e.Args { + args = append(args, e.String()) + } + return e.Func.String() + "(" + strings.Join(args, ", ") + ")" +} + +// CharLit represents a character literal. +type CharLit struct { + Value rune + ValuePos Pos + Literal string +} + +func (e *CharLit) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *CharLit) Pos() Pos { + return e.ValuePos +} + +// End returns the position of first character immediately after the node. +func (e *CharLit) End() Pos { + return Pos(int(e.ValuePos) + len(e.Literal)) +} + +func (e *CharLit) String() string { + return e.Literal +} + +// CondExpr represents a ternary conditional expression. +type CondExpr struct { + Cond Expr + True Expr + False Expr + QuestionPos Pos + ColonPos Pos +} + +func (e *CondExpr) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *CondExpr) Pos() Pos { + return e.Cond.Pos() +} + +// End returns the position of first character immediately after the node. +func (e *CondExpr) End() Pos { + return e.False.End() +} + +func (e *CondExpr) String() string { + return "(" + e.Cond.String() + " ? " + e.True.String() + + " : " + e.False.String() + ")" +} + +// ErrorExpr represents an error expression +type ErrorExpr struct { + Expr Expr + ErrorPos Pos + LParen Pos + RParen Pos +} + +func (e *ErrorExpr) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *ErrorExpr) Pos() Pos { + return e.ErrorPos +} + +// End returns the position of first character immediately after the node. +func (e *ErrorExpr) End() Pos { + return e.RParen +} + +func (e *ErrorExpr) String() string { + return "error(" + e.Expr.String() + ")" +} + +// FloatLit represents a floating point literal. +type FloatLit struct { + Value float64 + ValuePos Pos + Literal string +} + +func (e *FloatLit) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *FloatLit) Pos() Pos { + return e.ValuePos +} + +// End returns the position of first character immediately after the node. +func (e *FloatLit) End() Pos { + return Pos(int(e.ValuePos) + len(e.Literal)) +} + +func (e *FloatLit) String() string { + return e.Literal +} + +// FuncLit represents a function literal. +type FuncLit struct { + Type *FuncType + Body *BlockStmt +} + +func (e *FuncLit) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *FuncLit) Pos() Pos { + return e.Type.Pos() +} + +// End returns the position of first character immediately after the node. +func (e *FuncLit) End() Pos { + return e.Body.End() +} + +func (e *FuncLit) String() string { + return "func" + e.Type.Params.String() + " " + e.Body.String() +} + +// FuncType represents a function type definition. +type FuncType struct { + FuncPos Pos + Params *IdentList +} + +func (e *FuncType) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *FuncType) Pos() Pos { + return e.FuncPos +} + +// End returns the position of first character immediately after the node. +func (e *FuncType) End() Pos { + return e.Params.End() +} + +func (e *FuncType) String() string { + return "func" + e.Params.String() +} + +// Ident represents an identifier. +type Ident struct { + Name string + NamePos Pos +} + +func (e *Ident) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *Ident) Pos() Pos { + return e.NamePos +} + +// End returns the position of first character immediately after the node. +func (e *Ident) End() Pos { + return Pos(int(e.NamePos) + len(e.Name)) +} + +func (e *Ident) String() string { + if e != nil { + return e.Name + } + return nullRep +} + +// ImmutableExpr represents an immutable expression +type ImmutableExpr struct { + Expr Expr + ErrorPos Pos + LParen Pos + RParen Pos +} + +func (e *ImmutableExpr) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *ImmutableExpr) Pos() Pos { + return e.ErrorPos +} + +// End returns the position of first character immediately after the node. +func (e *ImmutableExpr) End() Pos { + return e.RParen +} + +func (e *ImmutableExpr) String() string { + return "immutable(" + e.Expr.String() + ")" +} + +// ImportExpr represents an import expression +type ImportExpr struct { + ModuleName string + Token token.Token + TokenPos Pos +} + +func (e *ImportExpr) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *ImportExpr) Pos() Pos { + return e.TokenPos +} + +// End returns the position of first character immediately after the node. +func (e *ImportExpr) End() Pos { + // import("moduleName") + return Pos(int(e.TokenPos) + 10 + len(e.ModuleName)) +} + +func (e *ImportExpr) String() string { + return `import("` + e.ModuleName + `")"` +} + +// IndexExpr represents an index expression. +type IndexExpr struct { + Expr Expr + LBrack Pos + Index Expr + RBrack Pos +} + +func (e *IndexExpr) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *IndexExpr) Pos() Pos { + return e.Expr.Pos() +} + +// End returns the position of first character immediately after the node. +func (e *IndexExpr) End() Pos { + return e.RBrack + 1 +} + +func (e *IndexExpr) String() string { + var index string + if e.Index != nil { + index = e.Index.String() + } + return e.Expr.String() + "[" + index + "]" +} + +// IntLit represents an integer literal. +type IntLit struct { + Value int64 + ValuePos Pos + Literal string +} + +func (e *IntLit) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *IntLit) Pos() Pos { + return e.ValuePos +} + +// End returns the position of first character immediately after the node. +func (e *IntLit) End() Pos { + return Pos(int(e.ValuePos) + len(e.Literal)) +} + +func (e *IntLit) String() string { + return e.Literal +} + +// MapElementLit represents a map element. +type MapElementLit struct { + Key string + KeyPos Pos + ColonPos Pos + Value Expr +} + +func (e *MapElementLit) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *MapElementLit) Pos() Pos { + return e.KeyPos +} + +// End returns the position of first character immediately after the node. +func (e *MapElementLit) End() Pos { + return e.Value.End() +} + +func (e *MapElementLit) String() string { + return e.Key + ": " + e.Value.String() +} + +// MapLit represents a map literal. +type MapLit struct { + LBrace Pos + Elements []*MapElementLit + RBrace Pos +} + +func (e *MapLit) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *MapLit) Pos() Pos { + return e.LBrace +} + +// End returns the position of first character immediately after the node. +func (e *MapLit) End() Pos { + return e.RBrace + 1 +} + +func (e *MapLit) String() string { + var elements []string + for _, m := range e.Elements { + elements = append(elements, m.String()) + } + return "{" + strings.Join(elements, ", ") + "}" +} + +// ParenExpr represents a parenthesis wrapped expression. +type ParenExpr struct { + Expr Expr + LParen Pos + RParen Pos +} + +func (e *ParenExpr) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *ParenExpr) Pos() Pos { + return e.LParen +} + +// End returns the position of first character immediately after the node. +func (e *ParenExpr) End() Pos { + return e.RParen + 1 +} + +func (e *ParenExpr) String() string { + return "(" + e.Expr.String() + ")" +} + +// SelectorExpr represents a selector expression. +type SelectorExpr struct { + Expr Expr + Sel Expr +} + +func (e *SelectorExpr) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *SelectorExpr) Pos() Pos { + return e.Expr.Pos() +} + +// End returns the position of first character immediately after the node. +func (e *SelectorExpr) End() Pos { + return e.Sel.End() +} + +func (e *SelectorExpr) String() string { + return e.Expr.String() + "." + e.Sel.String() +} + +// SliceExpr represents a slice expression. +type SliceExpr struct { + Expr Expr + LBrack Pos + Low Expr + High Expr + RBrack Pos +} + +func (e *SliceExpr) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *SliceExpr) Pos() Pos { + return e.Expr.Pos() +} + +// End returns the position of first character immediately after the node. +func (e *SliceExpr) End() Pos { + return e.RBrack + 1 +} + +func (e *SliceExpr) String() string { + var low, high string + if e.Low != nil { + low = e.Low.String() + } + if e.High != nil { + high = e.High.String() + } + return e.Expr.String() + "[" + low + ":" + high + "]" +} + +// StringLit represents a string literal. +type StringLit struct { + Value string + ValuePos Pos + Literal string +} + +func (e *StringLit) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *StringLit) Pos() Pos { + return e.ValuePos +} + +// End returns the position of first character immediately after the node. +func (e *StringLit) End() Pos { + return Pos(int(e.ValuePos) + len(e.Literal)) +} + +func (e *StringLit) String() string { + return e.Literal +} + +// UnaryExpr represents an unary operator expression. +type UnaryExpr struct { + Expr Expr + Token token.Token + TokenPos Pos +} + +func (e *UnaryExpr) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *UnaryExpr) Pos() Pos { + return e.Expr.Pos() +} + +// End returns the position of first character immediately after the node. +func (e *UnaryExpr) End() Pos { + return e.Expr.End() +} + +func (e *UnaryExpr) String() string { + return "(" + e.Token.String() + e.Expr.String() + ")" +} + +// UndefinedLit represents an undefined literal. +type UndefinedLit struct { + TokenPos Pos +} + +func (e *UndefinedLit) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *UndefinedLit) Pos() Pos { + return e.TokenPos +} + +// End returns the position of first character immediately after the node. +func (e *UndefinedLit) End() Pos { + return e.TokenPos + 9 // len(undefined) == 9 +} + +func (e *UndefinedLit) String() string { + return "undefined" +} diff --git a/compiler/ast/file.go b/internal/file.go similarity index 62% rename from compiler/ast/file.go rename to internal/file.go index fc18b2d..295153e 100644 --- a/compiler/ast/file.go +++ b/internal/file.go @@ -1,25 +1,23 @@ -package ast +package internal import ( "strings" - - "github.com/d5/tengo/compiler/source" ) // File represents a file unit. type File struct { - InputFile *source.File + InputFile *SourceFile Stmts []Stmt } // Pos returns the position of first character belonging to the node. -func (n *File) Pos() source.Pos { - return source.Pos(n.InputFile.Base) +func (n *File) Pos() Pos { + return Pos(n.InputFile.Base) } // End returns the position of first character immediately after the node. -func (n *File) End() source.Pos { - return source.Pos(n.InputFile.Base + n.InputFile.Size) +func (n *File) End() Pos { + return Pos(n.InputFile.Base + n.InputFile.Size) } func (n *File) String() string { @@ -27,6 +25,5 @@ func (n *File) String() string { for _, e := range n.Stmts { stmts = append(stmts, e.String()) } - return strings.Join(stmts, "; ") } diff --git a/compiler/instructions.go b/internal/internal.go similarity index 50% rename from compiler/instructions.go rename to internal/internal.go index 14dde1d..79f430b 100644 --- a/compiler/instructions.go +++ b/internal/internal.go @@ -1,8 +1,6 @@ -package compiler +package internal -import ( - "fmt" -) +import "fmt" // MakeInstruction returns a bytecode for an opcode and the operands. func MakeInstruction(opcode Opcode, operands ...int) []byte { @@ -14,7 +12,7 @@ func MakeInstruction(opcode Opcode, operands ...int) []byte { } instruction := make([]byte, totalLen) - instruction[0] = byte(opcode) + instruction[0] = opcode offset := 1 for i, o := range operands { @@ -29,44 +27,31 @@ func MakeInstruction(opcode Opcode, operands ...int) []byte { } offset += width } - return instruction } -// FormatInstructions returns string representation of -// bytecode instructions. +// FormatInstructions returns string representation of bytecode instructions. func FormatInstructions(b []byte, posOffset int) []string { var out []string i := 0 for i < len(b) { - numOperands := OpcodeOperands[Opcode(b[i])] + numOperands := OpcodeOperands[b[i]] operands, read := ReadOperands(numOperands, b[i+1:]) switch len(numOperands) { case 0: - out = append(out, fmt.Sprintf("%04d %-7s", posOffset+i, OpcodeNames[Opcode(b[i])])) + out = append(out, fmt.Sprintf("%04d %-7s", + posOffset+i, OpcodeNames[b[i]])) case 1: - out = append(out, fmt.Sprintf("%04d %-7s %-5d", posOffset+i, OpcodeNames[Opcode(b[i])], operands[0])) + out = append(out, fmt.Sprintf("%04d %-7s %-5d", + posOffset+i, OpcodeNames[b[i]], operands[0])) case 2: - out = append(out, fmt.Sprintf("%04d %-7s %-5d %-5d", posOffset+i, OpcodeNames[Opcode(b[i])], operands[0], operands[1])) + out = append(out, fmt.Sprintf("%04d %-7s %-5d %-5d", + posOffset+i, OpcodeNames[b[i]], + operands[0], operands[1])) } - i += 1 + read } - return out } - -func iterateInstructions(b []byte, fn func(pos int, opcode Opcode, operands []int) bool) { - for i := 0; i < len(b); i++ { - numOperands := OpcodeOperands[Opcode(b[i])] - operands, read := ReadOperands(numOperands, b[i+1:]) - - if !fn(i, b[i], operands) { - break - } - - i += read - } -} diff --git a/compiler/opcodes.go b/internal/opcodes.go similarity index 95% rename from compiler/opcodes.go rename to internal/opcodes.go index d832ee1..2890ec6 100644 --- a/compiler/opcodes.go +++ b/internal/opcodes.go @@ -1,4 +1,4 @@ -package compiler +package internal // Opcode represents a single byte operation code. type Opcode = byte @@ -45,10 +45,11 @@ const ( OpIteratorNext // Iterator next OpIteratorKey // Iterator key OpIteratorValue // Iterator value - OpBinaryOp // Binary Operation + OpBinaryOp // Binary operation + OpSuspend // Suspend VM ) -// OpcodeNames is opcode names. +// OpcodeNames are string representation of opcodes. var OpcodeNames = [...]string{ OpConstant: "CONST", OpPop: "POP", @@ -91,6 +92,7 @@ var OpcodeNames = [...]string{ OpIteratorKey: "ITKEY", OpIteratorValue: "ITVAL", OpBinaryOp: "BINARYOP", + OpSuspend: "SUSPEND", } // OpcodeOperands is the number of operands. @@ -136,6 +138,7 @@ var OpcodeOperands = [...][]int{ OpIteratorKey: {}, OpIteratorValue: {}, OpBinaryOp: {1}, + OpSuspend: {}, } // ReadOperands reads operands from the bytecode. @@ -147,9 +150,7 @@ func ReadOperands(numOperands []int, ins []byte) (operands []int, offset int) { case 2: operands = append(operands, int(ins[offset+1])|int(ins[offset])<<8) } - offset += width } - return } diff --git a/compiler/parser/parser.go b/internal/parser.go similarity index 66% rename from compiler/parser/parser.go rename to internal/parser.go index 27dd48f..a790973 100644 --- a/compiler/parser/parser.go +++ b/internal/parser.go @@ -1,63 +1,128 @@ -/* - Parser parses the Tengo source files. - - Parser is a modified version of Go's parser implementation. - - Copyright 2009 The Go Authors. All rights reserved. - Use of this source code is governed by a BSD-style - license that can be found in the LICENSE file. -*/ - -package parser +package internal import ( "fmt" "io" + "sort" "strconv" - "github.com/d5/tengo/compiler/ast" - "github.com/d5/tengo/compiler/scanner" - "github.com/d5/tengo/compiler/source" - "github.com/d5/tengo/compiler/token" + "github.com/d5/tengo/internal/token" ) type bailout struct{} -// Parser parses the Tengo source files. +var stmtStart = map[token.Token]bool{ + token.Break: true, + token.Continue: true, + token.For: true, + token.If: true, + token.Return: true, + token.Export: true, +} + +// ParserError represents a parser error. +type ParserError struct { + Pos SourceFilePos + Msg string +} + +func (e ParserError) Error() string { + if e.Pos.Filename != "" || e.Pos.IsValid() { + return fmt.Sprintf("Parse Error: %s\n\tat %s", e.Msg, e.Pos) + } + return fmt.Sprintf("Parse Error: %s", e.Msg) +} + +// ParserErrorList is a collection of parser errors. +type ParserErrorList []*ParserError + +// Add adds a new parser error to the collection. +func (p *ParserErrorList) Add(pos SourceFilePos, msg string) { + *p = append(*p, &ParserError{pos, msg}) +} + +// Len returns the number of elements in the collection. +func (p ParserErrorList) Len() int { + return len(p) +} + +func (p ParserErrorList) Swap(i, j int) { + p[i], p[j] = p[j], p[i] +} + +func (p ParserErrorList) Less(i, j int) bool { + e := &p[i].Pos + f := &p[j].Pos + + if e.Filename != f.Filename { + return e.Filename < f.Filename + } + if e.Line != f.Line { + return e.Line < f.Line + } + if e.Column != f.Column { + return e.Column < f.Column + } + return p[i].Msg < p[j].Msg +} + +// Sort sorts the collection. +func (p ParserErrorList) Sort() { + sort.Sort(p) +} + +func (p ParserErrorList) Error() string { + switch len(p) { + case 0: + return "no errors" + case 1: + return p[0].Error() + } + return fmt.Sprintf("%s (and %d more errors)", p[0], len(p)-1) +} + +// Err returns an error. +func (p ParserErrorList) Err() error { + if len(p) == 0 { + return nil + } + return p +} + +// Parser parses the Tengo source files. It's based on Go's parser +// implementation. type Parser struct { - file *source.File - errors ErrorList - scanner *scanner.Scanner - pos source.Pos + file *SourceFile + errors ParserErrorList + scanner *Scanner + pos Pos token token.Token tokenLit string - exprLevel int // < 0: in control clause, >= 0: in expression - syncPos source.Pos // last sync position - syncCount int // number of advance calls without progress + exprLevel int // < 0: in control clause, >= 0: in expression + syncPos Pos // last sync position + syncCount int // number of advance calls without progress trace bool indent int traceOut io.Writer } // NewParser creates a Parser. -func NewParser(file *source.File, src []byte, trace io.Writer) *Parser { +func NewParser(file *SourceFile, src []byte, trace io.Writer) *Parser { p := &Parser{ file: file, trace: trace != nil, traceOut: trace, } - - p.scanner = scanner.NewScanner(p.file, src, func(pos source.FilePos, msg string) { - p.errors.Add(pos, msg) - }, 0) - + p.scanner = NewScanner(p.file, src, + func(pos SourceFilePos, msg string) { + p.errors.Add(pos, msg) + }, 0) p.next() - return p } // ParseFile parses the source and returns an AST file unit. -func (p *Parser) ParseFile() (file *ast.File, err error) { +func (p *Parser) ParseFile() (file *File, err error) { defer func() { if e := recover(); e != nil { if _, ok := e.(bailout); !ok { @@ -70,7 +135,7 @@ func (p *Parser) ParseFile() (file *ast.File, err error) { }() if p.trace { - defer un(trace(p, "File")) + defer untracep(tracep(p, "File")) } if p.errors.Len() > 0 { @@ -82,17 +147,16 @@ func (p *Parser) ParseFile() (file *ast.File, err error) { return nil, p.errors.Err() } - file = &ast.File{ + file = &File{ InputFile: p.file, Stmts: stmts, } - return } -func (p *Parser) parseExpr() ast.Expr { +func (p *Parser) parseExpr() Expr { if p.trace { - defer un(trace(p, "Expression")) + defer untracep(tracep(p, "Expression")) } expr := p.parseBinaryExpr(token.LowestPrec + 1) @@ -101,13 +165,12 @@ func (p *Parser) parseExpr() ast.Expr { if p.token == token.Question { return p.parseCondExpr(expr) } - return expr } -func (p *Parser) parseBinaryExpr(prec1 int) ast.Expr { +func (p *Parser) parseBinaryExpr(prec1 int) Expr { if p.trace { - defer un(trace(p, "BinaryExpression")) + defer untracep(tracep(p, "BinaryExpression")) } x := p.parseUnaryExpr() @@ -122,7 +185,7 @@ func (p *Parser) parseBinaryExpr(prec1 int) ast.Expr { y := p.parseBinaryExpr(prec + 1) - x = &ast.BinaryExpr{ + x = &BinaryExpr{ LHS: x, RHS: y, Token: op, @@ -131,16 +194,13 @@ func (p *Parser) parseBinaryExpr(prec1 int) ast.Expr { } } -func (p *Parser) parseCondExpr(cond ast.Expr) ast.Expr { +func (p *Parser) parseCondExpr(cond Expr) Expr { questionPos := p.expect(token.Question) - trueExpr := p.parseExpr() - colonPos := p.expect(token.Colon) - falseExpr := p.parseExpr() - return &ast.CondExpr{ + return &CondExpr{ Cond: cond, True: trueExpr, False: falseExpr, @@ -149,9 +209,9 @@ func (p *Parser) parseCondExpr(cond ast.Expr) ast.Expr { } } -func (p *Parser) parseUnaryExpr() ast.Expr { +func (p *Parser) parseUnaryExpr() Expr { if p.trace { - defer un(trace(p, "UnaryExpression")) + defer untracep(tracep(p, "UnaryExpression")) } switch p.token { @@ -159,19 +219,18 @@ func (p *Parser) parseUnaryExpr() ast.Expr { pos, op := p.pos, p.token p.next() x := p.parseUnaryExpr() - return &ast.UnaryExpr{ + return &UnaryExpr{ Token: op, TokenPos: pos, Expr: x, } } - return p.parsePrimaryExpr() } -func (p *Parser) parsePrimaryExpr() ast.Expr { +func (p *Parser) parsePrimaryExpr() Expr { if p.trace { - defer un(trace(p, "PrimaryExpression")) + defer untracep(tracep(p, "PrimaryExpression")) } x := p.parseOperand() @@ -189,7 +248,7 @@ L: pos := p.pos p.errorExpected(pos, "selector") p.advance(stmtStart) - return &ast.BadExpr{From: pos, To: p.pos} + return &BadExpr{From: pos, To: p.pos} } case token.LBrack: x = p.parseIndexOrSlice(x) @@ -199,19 +258,18 @@ L: break L } } - return x } -func (p *Parser) parseCall(x ast.Expr) *ast.CallExpr { +func (p *Parser) parseCall(x Expr) *CallExpr { if p.trace { - defer un(trace(p, "Call")) + defer untracep(tracep(p, "Call")) } lparen := p.expect(token.LParen) p.exprLevel++ - var list []ast.Expr + var list []Expr for p.token != token.RParen && p.token != token.EOF { list = append(list, p.parseExpr()) @@ -222,8 +280,7 @@ func (p *Parser) parseCall(x ast.Expr) *ast.CallExpr { p.exprLevel-- rparen := p.expect(token.RParen) - - return &ast.CallExpr{ + return &CallExpr{ Func: x, LParen: lparen, RParen: rparen, @@ -239,26 +296,24 @@ func (p *Parser) expectComma(closing token.Token, want string) bool { p.errorExpected(p.pos, want) return false } - return true } if p.token == token.Semicolon && p.tokenLit == "\n" { p.next() } - return false } -func (p *Parser) parseIndexOrSlice(x ast.Expr) ast.Expr { +func (p *Parser) parseIndexOrSlice(x Expr) Expr { if p.trace { - defer un(trace(p, "IndexOrSlice")) + defer untracep(tracep(p, "IndexOrSlice")) } lbrack := p.expect(token.LBrack) p.exprLevel++ - var index [2]ast.Expr + var index [2]Expr if p.token != token.Colon { index[0] = p.parseExpr() } @@ -277,7 +332,7 @@ func (p *Parser) parseIndexOrSlice(x ast.Expr) ast.Expr { if numColons > 0 { // slice expression - return &ast.SliceExpr{ + return &SliceExpr{ Expr: x, LBrack: lbrack, RBrack: rbrack, @@ -285,8 +340,7 @@ func (p *Parser) parseIndexOrSlice(x ast.Expr) ast.Expr { High: index[1], } } - - return &ast.IndexExpr{ + return &IndexExpr{ Expr: x, LBrack: lbrack, RBrack: rbrack, @@ -294,88 +348,78 @@ func (p *Parser) parseIndexOrSlice(x ast.Expr) ast.Expr { } } -func (p *Parser) parseSelector(x ast.Expr) ast.Expr { +func (p *Parser) parseSelector(x Expr) Expr { if p.trace { - defer un(trace(p, "Selector")) + defer untracep(tracep(p, "Selector")) } sel := p.parseIdent() - - return &ast.SelectorExpr{Expr: x, Sel: &ast.StringLit{ + return &SelectorExpr{Expr: x, Sel: &StringLit{ Value: sel.Name, ValuePos: sel.NamePos, Literal: sel.Name, }} } -func (p *Parser) parseOperand() ast.Expr { +func (p *Parser) parseOperand() Expr { if p.trace { - defer un(trace(p, "Operand")) + defer untracep(tracep(p, "Operand")) } switch p.token { case token.Ident: return p.parseIdent() - case token.Int: v, _ := strconv.ParseInt(p.tokenLit, 10, 64) - x := &ast.IntLit{ + x := &IntLit{ Value: v, ValuePos: p.pos, Literal: p.tokenLit, } p.next() return x - case token.Float: v, _ := strconv.ParseFloat(p.tokenLit, 64) - x := &ast.FloatLit{ + x := &FloatLit{ Value: v, ValuePos: p.pos, Literal: p.tokenLit, } p.next() return x - case token.Char: return p.parseCharLit() - case token.String: v, _ := strconv.Unquote(p.tokenLit) - x := &ast.StringLit{ + x := &StringLit{ Value: v, ValuePos: p.pos, Literal: p.tokenLit, } p.next() return x - case token.True: - x := &ast.BoolLit{ + x := &BoolLit{ Value: true, ValuePos: p.pos, Literal: p.tokenLit, } p.next() return x - case token.False: - x := &ast.BoolLit{ + x := &BoolLit{ Value: false, ValuePos: p.pos, Literal: p.tokenLit, } p.next() return x - case token.Undefined: - x := &ast.UndefinedLit{TokenPos: p.pos} + x := &UndefinedLit{TokenPos: p.pos} p.next() return x - case token.Import: return p.parseImportExpr() - case token.LParen: lparen := p.pos p.next() @@ -383,24 +427,19 @@ func (p *Parser) parseOperand() ast.Expr { x := p.parseExpr() p.exprLevel-- rparen := p.expect(token.RParen) - return &ast.ParenExpr{ + return &ParenExpr{ LParen: lparen, Expr: x, RParen: rparen, } - case token.LBrack: // array literal return p.parseArrayLit() - case token.LBrace: // map literal return p.parseMapLit() - case token.Func: // function literal return p.parseFuncLit() - case token.Error: // error expression return p.parseErrorExpr() - case token.Immutable: // immutable expression return p.parseImmutableExpr() } @@ -408,43 +447,38 @@ func (p *Parser) parseOperand() ast.Expr { pos := p.pos p.errorExpected(pos, "operand") p.advance(stmtStart) - return &ast.BadExpr{From: pos, To: p.pos} + return &BadExpr{From: pos, To: p.pos} } -func (p *Parser) parseImportExpr() ast.Expr { +func (p *Parser) parseImportExpr() Expr { pos := p.pos - p.next() - p.expect(token.LParen) - if p.token != token.String { p.errorExpected(p.pos, "module name") p.advance(stmtStart) - return &ast.BadExpr{From: pos, To: p.pos} + return &BadExpr{From: pos, To: p.pos} } // module name moduleName, _ := strconv.Unquote(p.tokenLit) - - expr := &ast.ImportExpr{ + expr := &ImportExpr{ ModuleName: moduleName, Token: token.Import, TokenPos: pos, } p.next() - p.expect(token.RParen) - return expr } -func (p *Parser) parseCharLit() ast.Expr { +func (p *Parser) parseCharLit() Expr { if n := len(p.tokenLit); n >= 3 { - if code, _, _, err := strconv.UnquoteChar(p.tokenLit[1:n-1], '\''); err == nil { - x := &ast.CharLit{ - Value: rune(code), + code, _, _, err := strconv.UnquoteChar(p.tokenLit[1:n-1], '\'') + if err == nil { + x := &CharLit{ + Value: code, ValuePos: p.pos, Literal: p.tokenLit, } @@ -456,38 +490,36 @@ func (p *Parser) parseCharLit() ast.Expr { pos := p.pos p.error(pos, "illegal char literal") p.next() - return &ast.BadExpr{ + return &BadExpr{ From: pos, To: p.pos, } } -func (p *Parser) parseFuncLit() ast.Expr { +func (p *Parser) parseFuncLit() Expr { if p.trace { - defer un(trace(p, "FuncLit")) + defer untracep(tracep(p, "FuncLit")) } typ := p.parseFuncType() - p.exprLevel++ body := p.parseBody() p.exprLevel-- - - return &ast.FuncLit{ + return &FuncLit{ Type: typ, Body: body, } } -func (p *Parser) parseArrayLit() ast.Expr { +func (p *Parser) parseArrayLit() Expr { if p.trace { - defer un(trace(p, "ArrayLit")) + defer untracep(tracep(p, "ArrayLit")) } lbrack := p.expect(token.LBrack) p.exprLevel++ - var elements []ast.Expr + var elements []Expr for p.token != token.RBrack && p.token != token.EOF { elements = append(elements, p.parseExpr()) @@ -498,95 +530,83 @@ func (p *Parser) parseArrayLit() ast.Expr { p.exprLevel-- rbrack := p.expect(token.RBrack) - - return &ast.ArrayLit{ + return &ArrayLit{ Elements: elements, LBrack: lbrack, RBrack: rbrack, } } -func (p *Parser) parseErrorExpr() ast.Expr { +func (p *Parser) parseErrorExpr() Expr { pos := p.pos p.next() - lparen := p.expect(token.LParen) value := p.parseExpr() rparen := p.expect(token.RParen) - - expr := &ast.ErrorExpr{ + return &ErrorExpr{ ErrorPos: pos, Expr: value, LParen: lparen, RParen: rparen, } - - return expr } -func (p *Parser) parseImmutableExpr() ast.Expr { +func (p *Parser) parseImmutableExpr() Expr { pos := p.pos p.next() - lparen := p.expect(token.LParen) value := p.parseExpr() rparen := p.expect(token.RParen) - - expr := &ast.ImmutableExpr{ + return &ImmutableExpr{ ErrorPos: pos, Expr: value, LParen: lparen, RParen: rparen, } - - return expr } -func (p *Parser) parseFuncType() *ast.FuncType { +func (p *Parser) parseFuncType() *FuncType { if p.trace { - defer un(trace(p, "FuncType")) + defer untracep(tracep(p, "FuncType")) } pos := p.expect(token.Func) params := p.parseIdentList() - - return &ast.FuncType{ + return &FuncType{ FuncPos: pos, Params: params, } } -func (p *Parser) parseBody() *ast.BlockStmt { +func (p *Parser) parseBody() *BlockStmt { if p.trace { - defer un(trace(p, "Body")) + defer untracep(tracep(p, "Body")) } lbrace := p.expect(token.LBrace) list := p.parseStmtList() rbrace := p.expect(token.RBrace) - - return &ast.BlockStmt{ + return &BlockStmt{ LBrace: lbrace, RBrace: rbrace, Stmts: list, } } -func (p *Parser) parseStmtList() (list []ast.Stmt) { +func (p *Parser) parseStmtList() (list []Stmt) { if p.trace { - defer un(trace(p, "StatementList")) + defer untracep(tracep(p, "StatementList")) } for p.token != token.RBrace && p.token != token.EOF { list = append(list, p.parseStmt()) } - return } -func (p *Parser) parseIdent() *ast.Ident { +func (p *Parser) parseIdent() *Ident { pos := p.pos name := "_" @@ -596,19 +616,18 @@ func (p *Parser) parseIdent() *ast.Ident { } else { p.expect(token.Ident) } - - return &ast.Ident{ + return &Ident{ NamePos: pos, Name: name, } } -func (p *Parser) parseIdentList() *ast.IdentList { +func (p *Parser) parseIdentList() *IdentList { if p.trace { - defer un(trace(p, "IdentList")) + defer untracep(tracep(p, "IdentList")) } - var params []*ast.Ident + var params []*Ident lparen := p.expect(token.LParen) isVarArgs := false if p.token != token.RParen { @@ -629,8 +648,7 @@ func (p *Parser) parseIdentList() *ast.IdentList { } rparen := p.expect(token.RParen) - - return &ast.IdentList{ + return &IdentList{ LParen: lparen, RParen: rparen, VarArgs: isVarArgs, @@ -638,16 +656,18 @@ func (p *Parser) parseIdentList() *ast.IdentList { } } -func (p *Parser) parseStmt() (stmt ast.Stmt) { +func (p *Parser) parseStmt() (stmt Stmt) { if p.trace { - defer un(trace(p, "Statement")) + defer untracep(tracep(p, "Statement")) } switch p.token { case // simple statements - token.Func, token.Error, token.Immutable, token.Ident, token.Int, token.Float, token.Char, token.String, token.True, token.False, - token.Undefined, token.Import, token.LParen, token.LBrace, token.LBrack, - token.Add, token.Sub, token.Mul, token.And, token.Xor, token.Not: + token.Func, token.Error, token.Immutable, token.Ident, token.Int, + token.Float, token.Char, token.String, token.True, token.False, + token.Undefined, token.Import, token.LParen, token.LBrace, + token.LBrack, token.Add, token.Sub, token.Mul, token.And, token.Xor, + token.Not: s := p.parseSimpleStmt(false) p.expectSemi() return s @@ -662,23 +682,23 @@ func (p *Parser) parseStmt() (stmt ast.Stmt) { case token.Break, token.Continue: return p.parseBranchStmt(p.token) case token.Semicolon: - s := &ast.EmptyStmt{Semicolon: p.pos, Implicit: p.tokenLit == "\n"} + s := &EmptyStmt{Semicolon: p.pos, Implicit: p.tokenLit == "\n"} p.next() return s case token.RBrace: // semicolon may be omitted before a closing "}" - return &ast.EmptyStmt{Semicolon: p.pos, Implicit: true} + return &EmptyStmt{Semicolon: p.pos, Implicit: true} default: pos := p.pos p.errorExpected(pos, "statement") p.advance(stmtStart) - return &ast.BadStmt{From: pos, To: p.pos} + return &BadStmt{From: pos, To: p.pos} } } -func (p *Parser) parseForStmt() ast.Stmt { +func (p *Parser) parseForStmt() Stmt { if p.trace { - defer un(trace(p, "ForStmt")) + defer untracep(tracep(p, "ForStmt")) } pos := p.expect(token.For) @@ -688,7 +708,7 @@ func (p *Parser) parseForStmt() ast.Stmt { body := p.parseBlockStmt() p.expectSemi() - return &ast.ForStmt{ + return &ForStmt{ ForPos: pos, Body: body, } @@ -697,7 +717,7 @@ func (p *Parser) parseForStmt() ast.Stmt { prevLevel := p.exprLevel p.exprLevel = -1 - var s1 ast.Stmt + var s1 Stmt if p.token != token.Semicolon { // skipping init s1 = p.parseSimpleStmt(true) } @@ -705,7 +725,7 @@ func (p *Parser) parseForStmt() ast.Stmt { // for _ in seq {} or // for value in seq {} or // for key, value in seq {} - if forInStmt, isForIn := s1.(*ast.ForInStmt); isForIn { + if forInStmt, isForIn := s1.(*ForInStmt); isForIn { forInStmt.ForPos = pos p.exprLevel = prevLevel forInStmt.Body = p.parseBlockStmt() @@ -714,7 +734,7 @@ func (p *Parser) parseForStmt() ast.Stmt { } // for init; cond; post {} - var s2, s3 ast.Stmt + var s2, s3 Stmt if p.token == token.Semicolon { p.next() if p.token != token.Semicolon { @@ -734,50 +754,45 @@ func (p *Parser) parseForStmt() ast.Stmt { p.exprLevel = prevLevel body := p.parseBlockStmt() p.expectSemi() - cond := p.makeExpr(s2, "condition expression") - - return &ast.ForStmt{ + return &ForStmt{ ForPos: pos, Init: s1, Cond: cond, Post: s3, Body: body, } - } -func (p *Parser) parseBranchStmt(tok token.Token) ast.Stmt { +func (p *Parser) parseBranchStmt(tok token.Token) Stmt { if p.trace { - defer un(trace(p, "BranchStmt")) + defer untracep(tracep(p, "BranchStmt")) } pos := p.expect(tok) - var label *ast.Ident + var label *Ident if p.token == token.Ident { label = p.parseIdent() } p.expectSemi() - - return &ast.BranchStmt{ + return &BranchStmt{ Token: tok, TokenPos: pos, Label: label, } } -func (p *Parser) parseIfStmt() ast.Stmt { +func (p *Parser) parseIfStmt() Stmt { if p.trace { - defer un(trace(p, "IfStmt")) + defer untracep(tracep(p, "IfStmt")) } pos := p.expect(token.If) - init, cond := p.parseIfHeader() body := p.parseBlockStmt() - var elseStmt ast.Stmt + var elseStmt Stmt if p.token == token.Else { p.next() @@ -789,13 +804,12 @@ func (p *Parser) parseIfStmt() ast.Stmt { p.expectSemi() default: p.errorExpected(p.pos, "if or {") - elseStmt = &ast.BadStmt{From: p.pos, To: p.pos} + elseStmt = &BadStmt{From: p.pos, To: p.pos} } } else { p.expectSemi() } - - return &ast.IfStmt{ + return &IfStmt{ IfPos: pos, Init: init, Cond: cond, @@ -804,40 +818,37 @@ func (p *Parser) parseIfStmt() ast.Stmt { } } -func (p *Parser) parseBlockStmt() *ast.BlockStmt { +func (p *Parser) parseBlockStmt() *BlockStmt { if p.trace { - defer un(trace(p, "BlockStmt")) + defer untracep(tracep(p, "BlockStmt")) } lbrace := p.expect(token.LBrace) list := p.parseStmtList() rbrace := p.expect(token.RBrace) - - return &ast.BlockStmt{ + return &BlockStmt{ LBrace: lbrace, RBrace: rbrace, Stmts: list, } } -func (p *Parser) parseIfHeader() (init ast.Stmt, cond ast.Expr) { +func (p *Parser) parseIfHeader() (init Stmt, cond Expr) { if p.token == token.LBrace { p.error(p.pos, "missing condition in if statement") - cond = &ast.BadExpr{From: p.pos, To: p.pos} + cond = &BadExpr{From: p.pos, To: p.pos} return } outer := p.exprLevel p.exprLevel = -1 - if p.token == token.Semicolon { p.error(p.pos, "missing init in if statement") return } - init = p.parseSimpleStmt(false) - var condStmt ast.Stmt + var condStmt Stmt if p.token == token.LBrace { condStmt = init init = nil @@ -852,75 +863,67 @@ func (p *Parser) parseIfHeader() (init ast.Stmt, cond ast.Expr) { if condStmt != nil { cond = p.makeExpr(condStmt, "boolean expression") } - if cond == nil { - cond = &ast.BadExpr{From: p.pos, To: p.pos} + cond = &BadExpr{From: p.pos, To: p.pos} } - p.exprLevel = outer - return } -func (p *Parser) makeExpr(s ast.Stmt, want string) ast.Expr { +func (p *Parser) makeExpr(s Stmt, want string) Expr { if s == nil { return nil } - if es, isExpr := s.(*ast.ExprStmt); isExpr { + if es, isExpr := s.(*ExprStmt); isExpr { return es.Expr } found := "simple statement" - if _, isAss := s.(*ast.AssignStmt); isAss { + if _, isAss := s.(*AssignStmt); isAss { found = "assignment" } - p.error(s.Pos(), fmt.Sprintf("expected %s, found %s", want, found)) - - return &ast.BadExpr{From: s.Pos(), To: p.safePos(s.End())} + return &BadExpr{From: s.Pos(), To: p.safePos(s.End())} } -func (p *Parser) parseReturnStmt() ast.Stmt { +func (p *Parser) parseReturnStmt() Stmt { if p.trace { - defer un(trace(p, "ReturnStmt")) + defer untracep(tracep(p, "ReturnStmt")) } pos := p.pos p.expect(token.Return) - var x ast.Expr + var x Expr if p.token != token.Semicolon && p.token != token.RBrace { x = p.parseExpr() } p.expectSemi() - - return &ast.ReturnStmt{ + return &ReturnStmt{ ReturnPos: pos, Result: x, } } -func (p *Parser) parseExportStmt() ast.Stmt { +func (p *Parser) parseExportStmt() Stmt { if p.trace { - defer un(trace(p, "ExportStmt")) + defer untracep(tracep(p, "ExportStmt")) } pos := p.pos p.expect(token.Export) - x := p.parseExpr() p.expectSemi() - - return &ast.ExportStmt{ + return &ExportStmt{ ExportPos: pos, Result: x, } } -func (p *Parser) parseSimpleStmt(forIn bool) ast.Stmt { +func (p *Parser) parseSimpleStmt(forIn bool) Stmt { if p.trace { - defer un(trace(p, "SimpleStmt")) + defer untracep(tracep(p, "SimpleStmt")) } x := p.parseExprList() @@ -929,10 +932,8 @@ func (p *Parser) parseSimpleStmt(forIn bool) ast.Stmt { case token.Assign, token.Define: // assignment statement pos, tok := p.pos, p.token p.next() - y := p.parseExprList() - - return &ast.AssignStmt{ + return &AssignStmt{ LHS: x, RHS: y, Token: tok, @@ -941,35 +942,32 @@ func (p *Parser) parseSimpleStmt(forIn bool) ast.Stmt { case token.In: if forIn { p.next() - y := p.parseExpr() - var key, value *ast.Ident + var key, value *Ident var ok bool - switch len(x) { case 1: - key = &ast.Ident{Name: "_", NamePos: x[0].Pos()} + key = &Ident{Name: "_", NamePos: x[0].Pos()} - value, ok = x[0].(*ast.Ident) + value, ok = x[0].(*Ident) if !ok { p.errorExpected(x[0].Pos(), "identifier") - value = &ast.Ident{Name: "_", NamePos: x[0].Pos()} + value = &Ident{Name: "_", NamePos: x[0].Pos()} } case 2: - key, ok = x[0].(*ast.Ident) + key, ok = x[0].(*Ident) if !ok { p.errorExpected(x[0].Pos(), "identifier") - key = &ast.Ident{Name: "_", NamePos: x[0].Pos()} + key = &Ident{Name: "_", NamePos: x[0].Pos()} } - value, ok = x[1].(*ast.Ident) + value, ok = x[1].(*Ident) if !ok { p.errorExpected(x[1].Pos(), "identifier") - value = &ast.Ident{Name: "_", NamePos: x[1].Pos()} + value = &Ident{Name: "_", NamePos: x[1].Pos()} } } - - return &ast.ForInStmt{ + return &ForInStmt{ Key: key, Value: value, Iterable: y, @@ -984,33 +982,30 @@ func (p *Parser) parseSimpleStmt(forIn bool) ast.Stmt { switch p.token { case token.Define, - token.AddAssign, token.SubAssign, token.MulAssign, token.QuoAssign, token.RemAssign, - token.AndAssign, token.OrAssign, token.XorAssign, token.ShlAssign, token.ShrAssign, token.AndNotAssign: + token.AddAssign, token.SubAssign, token.MulAssign, token.QuoAssign, + token.RemAssign, token.AndAssign, token.OrAssign, token.XorAssign, + token.ShlAssign, token.ShrAssign, token.AndNotAssign: pos, tok := p.pos, p.token p.next() - y := p.parseExpr() - - return &ast.AssignStmt{ - LHS: []ast.Expr{x[0]}, - RHS: []ast.Expr{y}, + return &AssignStmt{ + LHS: []Expr{x[0]}, + RHS: []Expr{y}, Token: tok, TokenPos: pos, } case token.Inc, token.Dec: // increment or decrement statement - s := &ast.IncDecStmt{Expr: x[0], Token: p.token, TokenPos: p.pos} + s := &IncDecStmt{Expr: x[0], Token: p.token, TokenPos: p.pos} p.next() return s } - - // expression statement - return &ast.ExprStmt{Expr: x[0]} + return &ExprStmt{Expr: x[0]} } -func (p *Parser) parseExprList() (list []ast.Expr) { +func (p *Parser) parseExprList() (list []Expr) { if p.trace { - defer un(trace(p, "ExpressionList")) + defer untracep(tracep(p, "ExpressionList")) } list = append(list, p.parseExpr()) @@ -1018,18 +1013,16 @@ func (p *Parser) parseExprList() (list []ast.Expr) { p.next() list = append(list, p.parseExpr()) } - return } -func (p *Parser) parseMapElementLit() *ast.MapElementLit { +func (p *Parser) parseMapElementLit() *MapElementLit { if p.trace { - defer un(trace(p, "MapElementLit")) + defer untracep(tracep(p, "MapElementLit")) } pos := p.pos name := "_" - if p.token == token.Ident { name = p.tokenLit } else if p.token == token.String { @@ -1038,13 +1031,10 @@ func (p *Parser) parseMapElementLit() *ast.MapElementLit { } else { p.errorExpected(pos, "map key") } - p.next() - colonPos := p.expect(token.Colon) valueExpr := p.parseExpr() - - return &ast.MapElementLit{ + return &MapElementLit{ Key: name, KeyPos: pos, ColonPos: colonPos, @@ -1052,15 +1042,15 @@ func (p *Parser) parseMapElementLit() *ast.MapElementLit { } } -func (p *Parser) parseMapLit() *ast.MapLit { +func (p *Parser) parseMapLit() *MapLit { if p.trace { - defer un(trace(p, "MapLit")) + defer untracep(tracep(p, "MapLit")) } lbrace := p.expect(token.LBrace) p.exprLevel++ - var elements []*ast.MapElementLit + var elements []*MapElementLit for p.token != token.RBrace && p.token != token.EOF { elements = append(elements, p.parseMapElementLit()) @@ -1071,22 +1061,20 @@ func (p *Parser) parseMapLit() *ast.MapLit { p.exprLevel-- rbrace := p.expect(token.RBrace) - - return &ast.MapLit{ + return &MapLit{ LBrace: lbrace, RBrace: rbrace, Elements: elements, } } -func (p *Parser) expect(token token.Token) source.Pos { +func (p *Parser) expect(token token.Token) Pos { pos := p.pos if p.token != token { p.errorExpected(pos, "'"+token.String()+"'") } p.next() - return pos } @@ -1104,7 +1092,6 @@ func (p *Parser) expectSemi() { p.errorExpected(p.pos, "';'") p.advance(stmtStart) } - } func (p *Parser) advance(to map[token.Token]bool) { @@ -1114,7 +1101,6 @@ func (p *Parser) advance(to map[token.Token]bool) { p.syncCount++ return } - if p.pos > p.syncPos { p.syncPos = p.pos p.syncCount = 0 @@ -1124,7 +1110,7 @@ func (p *Parser) advance(to map[token.Token]bool) { } } -func (p *Parser) error(pos source.Pos, msg string) { +func (p *Parser) error(pos Pos, msg string) { filePos := p.file.Position(pos) n := len(p.errors) @@ -1132,16 +1118,14 @@ func (p *Parser) error(pos source.Pos, msg string) { // discard errors reported on the same line return } - if n > 10 { // too many errors; terminate early panic(bailout{}) } - p.errors.Add(filePos, msg) } -func (p *Parser) errorExpected(pos source.Pos, msg string) { +func (p *Parser) errorExpected(pos Pos, msg string) { msg = "expected " + msg if pos == p.pos { // error happened at the current position: provide more specific @@ -1154,7 +1138,6 @@ func (p *Parser) errorExpected(pos source.Pos, msg string) { msg += ", found '" + p.token.String() + "'" } } - p.error(pos, msg) } @@ -1170,19 +1153,18 @@ func (p *Parser) next() { p.printTrace(s) } } - p.token, p.tokenLit, p.pos = p.scanner.Scan() } func (p *Parser) printTrace(a ...interface{}) { const ( - dots = ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . " + dots = ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . " n = len(dots) ) filePos := p.file.Position(p.pos) - _, _ = fmt.Fprintf(p.traceOut, "%5d: %5d:%3d: ", p.pos, filePos.Line, filePos.Column) - + _, _ = fmt.Fprintf(p.traceOut, "%5d: %5d:%3d: ", p.pos, filePos.Line, + filePos.Column) i := 2 * p.indent for i > n { _, _ = fmt.Fprint(p.traceOut, dots) @@ -1192,25 +1174,23 @@ func (p *Parser) printTrace(a ...interface{}) { _, _ = fmt.Fprintln(p.traceOut, a...) } -func (p *Parser) safePos(pos source.Pos) source.Pos { +func (p *Parser) safePos(pos Pos) Pos { fileBase := p.file.Base fileSize := p.file.Size if int(pos) < fileBase || int(pos) > fileBase+fileSize { - return source.Pos(fileBase + fileSize) + return Pos(fileBase + fileSize) } - return pos } -func trace(p *Parser, msg string) *Parser { +func tracep(p *Parser, msg string) *Parser { p.printTrace(msg, "(") p.indent++ - return p } -func un(p *Parser) { +func untracep(p *Parser) { p.indent-- p.printTrace(")") } diff --git a/internal/parser_test.go b/internal/parser_test.go new file mode 100644 index 0000000..5a9ac41 --- /dev/null +++ b/internal/parser_test.go @@ -0,0 +1,2070 @@ +package internal_test + +import ( + "fmt" + "io" + "reflect" + "strings" + "testing" + + . "github.com/d5/tengo/internal" + "github.com/d5/tengo/internal/require" + "github.com/d5/tengo/internal/token" +) + +func TestParserError(t *testing.T) { + err := &ParserError{Pos: SourceFilePos{ + Offset: 10, Line: 1, Column: 10, + }, Msg: "test"} + require.Equal(t, "Parse Error: test\n\tat 1:10", err.Error()) +} + +func TestParserErrorList(t *testing.T) { + var list ParserErrorList + list.Add(SourceFilePos{Offset: 20, Line: 2, Column: 10}, "error 2") + list.Add(SourceFilePos{Offset: 30, Line: 3, Column: 10}, "error 3") + list.Add(SourceFilePos{Offset: 10, Line: 1, Column: 10}, "error 1") + list.Sort() + require.Equal(t, "Parse Error: error 1\n\tat 1:10 (and 2 more errors)", + list.Error()) +} + +func TestParseArray(t *testing.T) { + expectParse(t, "[1, 2, 3]", func(p pfn) []Stmt { + return stmts( + exprStmt( + arrayLit(p(1, 1), p(1, 9), + intLit(1, p(1, 2)), + intLit(2, p(1, 5)), + intLit(3, p(1, 8))))) + }) + + expectParse(t, ` +[ + 1, + 2, + 3 +]`, func(p pfn) []Stmt { + return stmts( + exprStmt( + arrayLit(p(2, 1), p(6, 1), + intLit(1, p(3, 2)), + intLit(2, p(4, 2)), + intLit(3, p(5, 2))))) + }) + expectParse(t, ` +[ + 1, + 2, + 3 + +]`, func(p pfn) []Stmt { + return stmts( + exprStmt( + arrayLit(p(2, 1), p(7, 1), + intLit(1, p(3, 2)), + intLit(2, p(4, 2)), + intLit(3, p(5, 2))))) + }) + + expectParse(t, `[1, "foo", 12.34]`, func(p pfn) []Stmt { + return stmts( + exprStmt( + arrayLit(p(1, 1), p(1, 17), + intLit(1, p(1, 2)), + stringLit("foo", p(1, 5)), + floatLit(12.34, p(1, 12))))) + }) + + expectParse(t, "a = [1, 2, 3]", func(p pfn) []Stmt { + return stmts( + assignStmt( + exprs(ident("a", p(1, 1))), + exprs(arrayLit(p(1, 5), p(1, 13), + intLit(1, p(1, 6)), + intLit(2, p(1, 9)), + intLit(3, p(1, 12)))), + token.Assign, + p(1, 3))) + }) + + expectParse(t, "a = [1 + 2, b * 4, [4, c]]", func(p pfn) []Stmt { + return stmts( + assignStmt( + exprs(ident("a", p(1, 1))), + exprs(arrayLit(p(1, 5), p(1, 26), + binaryExpr( + intLit(1, p(1, 6)), + intLit(2, p(1, 10)), + token.Add, + p(1, 8)), + binaryExpr( + ident("b", p(1, 13)), + intLit(4, p(1, 17)), + token.Mul, + p(1, 15)), + arrayLit(p(1, 20), p(1, 25), + intLit(4, p(1, 21)), + ident("c", p(1, 24))))), + token.Assign, + p(1, 3))) + }) + + expectParseError(t, `[1, 2, 3,]`) + expectParseError(t, ` +[ + 1, + 2, + 3, +]`) + expectParseError(t, ` +[ + 1, + 2, + 3, + +]`) + expectParseError(t, `[1, 2, 3, ,]`) +} + +func TestParseAssignment(t *testing.T) { + expectParse(t, "a = 5", func(p pfn) []Stmt { + return stmts( + assignStmt( + exprs(ident("a", p(1, 1))), + exprs(intLit(5, p(1, 5))), + token.Assign, + p(1, 3))) + }) + + expectParse(t, "a := 5", func(p pfn) []Stmt { + return stmts( + assignStmt( + exprs(ident("a", p(1, 1))), + exprs(intLit(5, p(1, 6))), + token.Define, + p(1, 3))) + }) + + expectParse(t, "a, b = 5, 10", func(p pfn) []Stmt { + return stmts( + assignStmt( + exprs( + ident("a", p(1, 1)), + ident("b", p(1, 4))), + exprs( + intLit(5, p(1, 8)), + intLit(10, p(1, 11))), + token.Assign, + p(1, 6))) + }) + + expectParse(t, "a, b := 5, 10", func(p pfn) []Stmt { + return stmts( + assignStmt( + exprs( + ident("a", p(1, 1)), + ident("b", p(1, 4))), + exprs( + intLit(5, p(1, 9)), + intLit(10, p(1, 12))), + token.Define, + p(1, 6))) + }) + + expectParse(t, "a, b = a + 2, b - 8", func(p pfn) []Stmt { + return stmts( + assignStmt( + exprs( + ident("a", p(1, 1)), + ident("b", p(1, 4))), + exprs( + binaryExpr( + ident("a", p(1, 8)), + intLit(2, p(1, 12)), + token.Add, + p(1, 10)), + binaryExpr( + ident("b", p(1, 15)), + intLit(8, p(1, 19)), + token.Sub, + p(1, 17))), + token.Assign, + p(1, 6))) + }) + + expectParse(t, "a = [1, 2, 3]", func(p pfn) []Stmt { + return stmts( + assignStmt( + exprs(ident("a", p(1, 1))), + exprs(arrayLit(p(1, 5), p(1, 13), + intLit(1, p(1, 6)), + intLit(2, p(1, 9)), + intLit(3, p(1, 12)))), + token.Assign, + p(1, 3))) + }) + + expectParse(t, "a = [1 + 2, b * 4, [4, c]]", func(p pfn) []Stmt { + return stmts( + assignStmt( + exprs(ident("a", p(1, 1))), + exprs(arrayLit(p(1, 5), p(1, 26), + binaryExpr( + intLit(1, p(1, 6)), + intLit(2, p(1, 10)), + token.Add, + p(1, 8)), + binaryExpr( + ident("b", p(1, 13)), + intLit(4, p(1, 17)), + token.Mul, + p(1, 15)), + arrayLit(p(1, 20), p(1, 25), + intLit(4, p(1, 21)), + ident("c", p(1, 24))))), + token.Assign, + p(1, 3))) + }) + + expectParse(t, "a += 5", func(p pfn) []Stmt { + return stmts( + assignStmt( + exprs(ident("a", p(1, 1))), + exprs(intLit(5, p(1, 6))), + token.AddAssign, + p(1, 3))) + }) + + expectParse(t, "a *= 5 + 10", func(p pfn) []Stmt { + return stmts( + assignStmt( + exprs(ident("a", p(1, 1))), + exprs( + binaryExpr( + intLit(5, p(1, 6)), + intLit(10, p(1, 10)), + token.Add, + p(1, 8))), + token.MulAssign, + p(1, 3))) + }) +} + +func TestParseBoolean(t *testing.T) { + expectParse(t, "true", func(p pfn) []Stmt { + return stmts( + exprStmt( + boolLit(true, p(1, 1)))) + }) + + expectParse(t, "false", func(p pfn) []Stmt { + return stmts( + exprStmt( + boolLit(false, p(1, 1)))) + }) + + expectParse(t, "true != false", func(p pfn) []Stmt { + return stmts( + exprStmt( + binaryExpr( + boolLit(true, p(1, 1)), + boolLit(false, p(1, 9)), + token.NotEqual, + p(1, 6)))) + }) + + expectParse(t, "!false", func(p pfn) []Stmt { + return stmts( + exprStmt( + unaryExpr( + boolLit(false, p(1, 2)), + token.Not, + p(1, 1)))) + }) +} + +func TestParseCall(t *testing.T) { + expectParse(t, "add(1, 2, 3)", func(p pfn) []Stmt { + return stmts( + exprStmt( + callExpr( + ident("add", p(1, 1)), + p(1, 4), p(1, 12), + intLit(1, p(1, 5)), + intLit(2, p(1, 8)), + intLit(3, p(1, 11))))) + }) + + expectParse(t, "a = add(1, 2, 3)", func(p pfn) []Stmt { + return stmts( + assignStmt( + exprs( + ident("a", p(1, 1))), + exprs( + callExpr( + ident("add", p(1, 5)), + p(1, 8), p(1, 16), + intLit(1, p(1, 9)), + intLit(2, p(1, 12)), + intLit(3, p(1, 15)))), + token.Assign, + p(1, 3))) + }) + + expectParse(t, "a, b = add(1, 2, 3)", func(p pfn) []Stmt { + return stmts( + assignStmt( + exprs( + ident("a", p(1, 1)), + ident("b", p(1, 4))), + exprs( + callExpr( + ident("add", p(1, 8)), + p(1, 11), p(1, 19), + intLit(1, p(1, 12)), + intLit(2, p(1, 15)), + intLit(3, p(1, 18)))), + token.Assign, + p(1, 6))) + }) + + expectParse(t, "add(a + 1, 2 * 1, (b + c))", func(p pfn) []Stmt { + return stmts( + exprStmt( + callExpr( + ident("add", p(1, 1)), + p(1, 4), p(1, 26), + binaryExpr( + ident("a", p(1, 5)), + intLit(1, p(1, 9)), + token.Add, + p(1, 7)), + binaryExpr( + intLit(2, p(1, 12)), + intLit(1, p(1, 16)), + token.Mul, + p(1, 14)), + parenExpr( + binaryExpr( + ident("b", p(1, 20)), + ident("c", p(1, 24)), + token.Add, + p(1, 22)), + p(1, 19), p(1, 25))))) + }) + + expectParseString(t, "a + add(b * c) + d", "((a + add((b * c))) + d)") + expectParseString(t, "add(a, b, 1, 2 * 3, 4 + 5, add(6, 7 * 8))", + "add(a, b, 1, (2 * 3), (4 + 5), add(6, (7 * 8)))") + expectParseString(t, "f1(a) + f2(b) * f3(c)", "(f1(a) + (f2(b) * f3(c)))") + expectParseString(t, "(f1(a) + f2(b)) * f3(c)", + "(((f1(a) + f2(b))) * f3(c))") + + expectParse(t, "func(a, b) { a + b }(1, 2)", func(p pfn) []Stmt { + return stmts( + exprStmt( + callExpr( + funcLit( + funcType( + identList( + p(1, 5), p(1, 10), + false, + ident("a", p(1, 6)), + ident("b", p(1, 9))), + p(1, 1)), + blockStmt( + p(1, 12), p(1, 20), + exprStmt( + binaryExpr( + ident("a", p(1, 14)), + ident("b", p(1, 18)), + token.Add, + p(1, 16))))), + p(1, 21), p(1, 26), + intLit(1, p(1, 22)), + intLit(2, p(1, 25))))) + }) + + expectParse(t, `a.b()`, func(p pfn) []Stmt { + return stmts( + exprStmt( + callExpr( + selectorExpr( + ident("a", p(1, 1)), + stringLit("b", p(1, 3))), + p(1, 4), p(1, 5)))) + }) + + expectParse(t, `a.b.c()`, func(p pfn) []Stmt { + return stmts( + exprStmt( + callExpr( + selectorExpr( + selectorExpr( + ident("a", p(1, 1)), + stringLit("b", p(1, 3))), + stringLit("c", p(1, 5))), + p(1, 6), p(1, 7)))) + }) + + expectParse(t, `a["b"].c()`, func(p pfn) []Stmt { + return stmts( + exprStmt( + callExpr( + selectorExpr( + indexExpr( + ident("a", p(1, 1)), + stringLit("b", p(1, 3)), + p(1, 2), p(1, 6)), + stringLit("c", p(1, 8))), + p(1, 9), p(1, 10)))) + }) +} + +func TestParseChar(t *testing.T) { + expectParse(t, `'A'`, func(p pfn) []Stmt { + return stmts( + exprStmt( + charLit('A', 1))) + }) + expectParse(t, `'九'`, func(p pfn) []Stmt { + return stmts( + exprStmt( + charLit('九', 1))) + }) + + expectParseError(t, `''`) + expectParseError(t, `'AB'`) + expectParseError(t, `'A九'`) +} + +func TestParseCondExpr(t *testing.T) { + expectParse(t, "a ? b : c", func(p pfn) []Stmt { + return stmts( + exprStmt( + condExpr( + ident("a", p(1, 1)), + ident("b", p(1, 5)), + ident("c", p(1, 9)), + p(1, 3), + p(1, 7)))) + }) + expectParse(t, `a ? +b : +c`, func(p pfn) []Stmt { + return stmts( + exprStmt( + condExpr( + ident("a", p(1, 1)), + ident("b", p(1, 5)), + ident("c", p(1, 9)), + p(1, 3), + p(1, 7)))) + }) + + expectParseString(t, `a ? b : c`, "(a ? b : c)") + expectParseString(t, `a + b ? c - d : e * f`, + "((a + b) ? (c - d) : (e * f))") + expectParseString(t, `a == b ? c + (d / e) : f ? g : h + i`, + "((a == b) ? (c + ((d / e))) : (f ? g : (h + i)))") + expectParseString(t, `(a + b) ? (c - d) : (e * f)`, + "(((a + b)) ? ((c - d)) : ((e * f)))") + expectParseString(t, `a + (b ? c : d) - e`, "((a + ((b ? c : d))) - e)") + expectParseString(t, `a ? b ? c : d : e`, "(a ? (b ? c : d) : e)") + expectParseString(t, `a := b ? c : d`, "a := (b ? c : d)") + expectParseString(t, `x := a ? b ? c : d : e`, + "x := (a ? (b ? c : d) : e)") + + // ? : should be at the end of each line if it's multi-line + expectParseError(t, `a +? b +: c`) + expectParseError(t, `a ? (b : e)`) + expectParseError(t, `(a ? b) : e`) +} + +func TestParseError(t *testing.T) { + expectParse(t, `error(1234)`, func(p pfn) []Stmt { + return stmts( + exprStmt( + errorExpr(p(1, 1), intLit(1234, p(1, 7)), p(1, 6), p(1, 11)))) + }) + + expectParse(t, `err1 := error("some error")`, func(p pfn) []Stmt { + return stmts( + assignStmt( + exprs(ident("err1", p(1, 1))), + exprs(errorExpr(p(1, 9), + stringLit("some error", p(1, 15)), p(1, 14), p(1, 27))), + token.Define, p(1, 6))) + }) + + expectParse(t, `return error("some error")`, func(p pfn) []Stmt { + return stmts( + returnStmt(p(1, 1), + errorExpr(p(1, 8), + stringLit("some error", p(1, 14)), p(1, 13), p(1, 26)))) + }) + + expectParse(t, `return error("some" + "error")`, func(p pfn) []Stmt { + return stmts( + returnStmt(p(1, 1), + errorExpr(p(1, 8), + binaryExpr( + stringLit("some", p(1, 14)), + stringLit("error", p(1, 23)), + token.Add, p(1, 21)), + p(1, 13), p(1, 30)))) + }) + + expectParseError(t, `error()`) // must have a value +} + +func TestParseForIn(t *testing.T) { + expectParse(t, "for x in y {}", func(p pfn) []Stmt { + return stmts( + forInStmt( + ident("_", p(1, 5)), + ident("x", p(1, 5)), + ident("y", p(1, 10)), + blockStmt(p(1, 12), p(1, 13)), + p(1, 1))) + }) + + expectParse(t, "for _ in y {}", func(p pfn) []Stmt { + return stmts( + forInStmt( + ident("_", p(1, 5)), + ident("_", p(1, 5)), + ident("y", p(1, 10)), + blockStmt(p(1, 12), p(1, 13)), + p(1, 1))) + }) + + expectParse(t, "for x in [1, 2, 3] {}", func(p pfn) []Stmt { + return stmts( + forInStmt( + ident("_", p(1, 5)), + ident("x", p(1, 5)), + arrayLit( + p(1, 10), p(1, 18), + intLit(1, p(1, 11)), + intLit(2, p(1, 14)), + intLit(3, p(1, 17))), + blockStmt(p(1, 20), p(1, 21)), + p(1, 1))) + }) + + expectParse(t, "for x, y in z {}", func(p pfn) []Stmt { + return stmts( + forInStmt( + ident("x", p(1, 5)), + ident("y", p(1, 8)), + ident("z", p(1, 13)), + blockStmt(p(1, 15), p(1, 16)), + p(1, 1))) + }) + + expectParse(t, "for x, y in {k1: 1, k2: 2} {}", func(p pfn) []Stmt { + return stmts( + forInStmt( + ident("x", p(1, 5)), + ident("y", p(1, 8)), + mapLit( + p(1, 13), p(1, 26), + mapElementLit( + "k1", p(1, 14), p(1, 16), intLit(1, p(1, 18))), + mapElementLit( + "k2", p(1, 21), p(1, 23), intLit(2, p(1, 25)))), + blockStmt(p(1, 28), p(1, 29)), + p(1, 1))) + }) +} + +func TestParseFor(t *testing.T) { + expectParse(t, "for {}", func(p pfn) []Stmt { + return stmts( + forStmt(nil, nil, nil, blockStmt(p(1, 5), p(1, 6)), p(1, 1))) + }) + + expectParse(t, "for a == 5 {}", func(p pfn) []Stmt { + return stmts( + forStmt( + nil, + binaryExpr( + ident("a", p(1, 5)), + intLit(5, p(1, 10)), + token.Equal, + p(1, 7)), + nil, + blockStmt(p(1, 12), p(1, 13)), + p(1, 1))) + }) + + expectParse(t, "for a := 0; a == 5; {}", func(p pfn) []Stmt { + return stmts( + forStmt( + assignStmt( + exprs(ident("a", p(1, 5))), + exprs(intLit(0, p(1, 10))), + token.Define, p(1, 7)), + binaryExpr( + ident("a", p(1, 13)), + intLit(5, p(1, 18)), + token.Equal, + p(1, 15)), + nil, + blockStmt(p(1, 22), p(1, 23)), + p(1, 1))) + }) + + expectParse(t, "for a := 0; a < 5; a++ {}", func(p pfn) []Stmt { + return stmts( + forStmt( + assignStmt( + exprs(ident("a", p(1, 5))), + exprs(intLit(0, p(1, 10))), + token.Define, p(1, 7)), + binaryExpr( + ident("a", p(1, 13)), + intLit(5, p(1, 17)), + token.Less, + p(1, 15)), + incDecStmt( + ident("a", p(1, 20)), + token.Inc, p(1, 21)), + blockStmt(p(1, 24), p(1, 25)), + p(1, 1))) + }) + + expectParse(t, "for ; a < 5; a++ {}", func(p pfn) []Stmt { + return stmts( + forStmt( + nil, + binaryExpr( + ident("a", p(1, 7)), + intLit(5, p(1, 11)), + token.Less, + p(1, 9)), + incDecStmt( + ident("a", p(1, 14)), + token.Inc, p(1, 15)), + blockStmt(p(1, 18), p(1, 19)), + p(1, 1))) + }) + + expectParse(t, "for a := 0; ; a++ {}", func(p pfn) []Stmt { + return stmts( + forStmt( + assignStmt( + exprs(ident("a", p(1, 5))), + exprs(intLit(0, p(1, 10))), + token.Define, p(1, 7)), + nil, + incDecStmt( + ident("a", p(1, 15)), + token.Inc, p(1, 16)), + blockStmt(p(1, 19), p(1, 20)), + p(1, 1))) + }) + + expectParse(t, "for a == 5 && b != 4 {}", func(p pfn) []Stmt { + return stmts( + forStmt( + nil, + binaryExpr( + binaryExpr( + ident("a", p(1, 5)), + intLit(5, p(1, 10)), + token.Equal, + p(1, 7)), + binaryExpr( + ident("b", p(1, 15)), + intLit(4, p(1, 20)), + token.NotEqual, + p(1, 17)), + token.LAnd, + p(1, 12)), + nil, + blockStmt(p(1, 22), p(1, 23)), + p(1, 1))) + }) +} + +func TestParseFunction(t *testing.T) { + expectParse(t, "a = func(b, c, d) { return d }", func(p pfn) []Stmt { + return stmts( + assignStmt( + exprs( + ident("a", p(1, 1))), + exprs( + funcLit( + funcType( + identList(p(1, 9), p(1, 17), false, + ident("b", p(1, 10)), + ident("c", p(1, 13)), + ident("d", p(1, 16))), + p(1, 5)), + blockStmt(p(1, 19), p(1, 30), + returnStmt(p(1, 21), ident("d", p(1, 28)))))), + token.Assign, + p(1, 3))) + }) +} + +func TestParseVariadicFunction(t *testing.T) { + expectParse(t, "a = func(...args) { return args }", func(p pfn) []Stmt { + return stmts( + assignStmt( + exprs( + ident("a", p(1, 1))), + exprs( + funcLit( + funcType( + identList( + p(1, 9), p(1, 17), + true, + ident("args", p(1, 13)), + ), p(1, 5)), + blockStmt(p(1, 19), p(1, 33), + returnStmt(p(1, 21), + ident("args", p(1, 28)), + ), + ), + ), + ), + token.Assign, + p(1, 3))) + }) +} + +func TestParseVariadicFunctionWithArgs(t *testing.T) { + expectParse(t, "a = func(x, y, ...z) { return z }", func(p pfn) []Stmt { + return stmts( + assignStmt( + exprs( + ident("a", p(1, 1))), + exprs( + funcLit( + funcType( + identList( + p(1, 9), p(1, 20), + true, + ident("x", p(1, 10)), + ident("y", p(1, 13)), + ident("z", p(1, 19)), + ), p(1, 5)), + blockStmt(p(1, 22), p(1, 33), + returnStmt(p(1, 24), + ident("z", p(1, 31)), + ), + ), + ), + ), + token.Assign, + p(1, 3))) + }) + + expectParseError(t, "a = func(x, y, ...z, invalid) { return z }") + expectParseError(t, "a = func(...args, invalid) { return args }") +} + +func TestParseIf(t *testing.T) { + expectParse(t, "if a == 5 {}", func(p pfn) []Stmt { + return stmts( + ifStmt( + nil, + binaryExpr( + ident("a", p(1, 4)), + intLit(5, p(1, 9)), + token.Equal, + p(1, 6)), + blockStmt( + p(1, 11), p(1, 12)), + nil, + p(1, 1))) + }) + + expectParse(t, "if a == 5 && b != 3 {}", func(p pfn) []Stmt { + return stmts( + ifStmt( + nil, + binaryExpr( + binaryExpr( + ident("a", p(1, 4)), + intLit(5, p(1, 9)), + token.Equal, + p(1, 6)), + binaryExpr( + ident("b", p(1, 14)), + intLit(3, p(1, 19)), + token.NotEqual, + p(1, 16)), + token.LAnd, + p(1, 11)), + blockStmt( + p(1, 21), p(1, 22)), + nil, + p(1, 1))) + }) + + expectParse(t, "if a == 5 { a = 3; a = 1 }", func(p pfn) []Stmt { + return stmts( + ifStmt( + nil, + binaryExpr( + ident("a", p(1, 4)), + intLit(5, p(1, 9)), + token.Equal, + p(1, 6)), + blockStmt( + p(1, 11), p(1, 26), + assignStmt( + exprs(ident("a", p(1, 13))), + exprs(intLit(3, p(1, 17))), + token.Assign, + p(1, 15)), + assignStmt( + exprs(ident("a", p(1, 20))), + exprs(intLit(1, p(1, 24))), + token.Assign, + p(1, 22))), + nil, + p(1, 1))) + }) + + expectParse(t, "if a == 5 { a = 3; a = 1 } else { a = 2; a = 4 }", + func(p pfn) []Stmt { + return stmts( + ifStmt( + nil, + binaryExpr( + ident("a", p(1, 4)), + intLit(5, p(1, 9)), + token.Equal, + p(1, 6)), + blockStmt( + p(1, 11), p(1, 26), + assignStmt( + exprs(ident("a", p(1, 13))), + exprs(intLit(3, p(1, 17))), + token.Assign, + p(1, 15)), + assignStmt( + exprs(ident("a", p(1, 20))), + exprs(intLit(1, p(1, 24))), + token.Assign, + p(1, 22))), + blockStmt( + p(1, 33), p(1, 48), + assignStmt( + exprs(ident("a", p(1, 35))), + exprs(intLit(2, p(1, 39))), + token.Assign, + p(1, 37)), + assignStmt( + exprs(ident("a", p(1, 42))), + exprs(intLit(4, p(1, 46))), + token.Assign, + p(1, 44))), + p(1, 1))) + }) + + expectParse(t, ` +if a == 5 { + b = 3 + c = 1 +} else if d == 3 { + e = 8 + f = 3 +} else { + g = 2 + h = 4 +}`, func(p pfn) []Stmt { + return stmts( + ifStmt( + nil, + binaryExpr( + ident("a", p(2, 4)), + intLit(5, p(2, 9)), + token.Equal, + p(2, 6)), + blockStmt( + p(2, 11), p(5, 1), + assignStmt( + exprs(ident("b", p(3, 2))), + exprs(intLit(3, p(3, 6))), + token.Assign, + p(3, 4)), + assignStmt( + exprs(ident("c", p(4, 2))), + exprs(intLit(1, p(4, 6))), + token.Assign, + p(4, 4))), + ifStmt( + nil, + binaryExpr( + ident("d", p(5, 11)), + intLit(3, p(5, 16)), + token.Equal, + p(5, 13)), + blockStmt( + p(5, 18), p(8, 1), + assignStmt( + exprs(ident("e", p(6, 2))), + exprs(intLit(8, p(6, 6))), + token.Assign, + p(6, 4)), + assignStmt( + exprs(ident("f", p(7, 2))), + exprs(intLit(3, p(7, 6))), + token.Assign, + p(7, 4))), + blockStmt( + p(8, 8), p(11, 1), + assignStmt( + exprs(ident("g", p(9, 2))), + exprs(intLit(2, p(9, 6))), + token.Assign, + p(9, 4)), + assignStmt( + exprs(ident("h", p(10, 2))), + exprs(intLit(4, p(10, 6))), + token.Assign, + p(10, 4))), + p(5, 8)), + p(2, 1))) + }) + + expectParse(t, "if a := 3; a < b {}", func(p pfn) []Stmt { + return stmts( + ifStmt( + assignStmt( + exprs(ident("a", p(1, 4))), + exprs(intLit(3, p(1, 9))), + token.Define, p(1, 6)), + binaryExpr( + ident("a", p(1, 12)), + ident("b", p(1, 16)), + token.Less, p(1, 14)), + blockStmt( + p(1, 18), p(1, 19)), + nil, + p(1, 1))) + }) + + expectParse(t, "if a++; a < b {}", func(p pfn) []Stmt { + return stmts( + ifStmt( + incDecStmt(ident("a", p(1, 4)), token.Inc, p(1, 5)), + binaryExpr( + ident("a", p(1, 9)), + ident("b", p(1, 13)), + token.Less, p(1, 11)), + blockStmt( + p(1, 15), p(1, 16)), + nil, + p(1, 1))) + }) + + expectParseError(t, `if {}`) + expectParseError(t, `if a == b { } else a != b { }`) + expectParseError(t, `if a == b { } else if { }`) + expectParseError(t, `else { }`) + expectParseError(t, `if ; {}`) + expectParseError(t, `if a := 3; {}`) + expectParseError(t, `if ; a < 3 {}`) +} + +func TestParseImport(t *testing.T) { + expectParse(t, `a := import("mod1")`, func(p pfn) []Stmt { + return stmts( + assignStmt( + exprs(ident("a", p(1, 1))), + exprs(importExpr("mod1", p(1, 6))), + token.Define, p(1, 3))) + }) + + expectParse(t, `import("mod1").var1`, func(p pfn) []Stmt { + return stmts( + exprStmt( + selectorExpr( + importExpr("mod1", p(1, 1)), + stringLit("var1", p(1, 16))))) + }) + + expectParse(t, `import("mod1").func1()`, func(p pfn) []Stmt { + return stmts( + exprStmt( + callExpr( + selectorExpr( + importExpr("mod1", p(1, 1)), + stringLit("func1", p(1, 16))), + p(1, 21), p(1, 22)))) + }) + + expectParse(t, `for x, y in import("mod1") {}`, func(p pfn) []Stmt { + return stmts( + forInStmt( + ident("x", p(1, 5)), + ident("y", p(1, 8)), + importExpr("mod1", p(1, 13)), + blockStmt(p(1, 28), p(1, 29)), + p(1, 1))) + }) +} + +func TestParseIndex(t *testing.T) { + expectParse(t, "[1, 2, 3][1]", func(p pfn) []Stmt { + return stmts( + exprStmt( + indexExpr( + arrayLit(p(1, 1), p(1, 9), + intLit(1, p(1, 2)), + intLit(2, p(1, 5)), + intLit(3, p(1, 8))), + intLit(1, p(1, 11)), + p(1, 10), p(1, 12)))) + }) + + expectParse(t, "[1, 2, 3][5 - a]", func(p pfn) []Stmt { + return stmts( + exprStmt( + indexExpr( + arrayLit(p(1, 1), p(1, 9), + intLit(1, p(1, 2)), + intLit(2, p(1, 5)), + intLit(3, p(1, 8))), + binaryExpr( + intLit(5, p(1, 11)), + ident("a", p(1, 15)), + token.Sub, + p(1, 13)), + p(1, 10), p(1, 16)))) + }) + + expectParse(t, "[1, 2, 3][5 : a]", func(p pfn) []Stmt { + return stmts( + exprStmt( + sliceExpr( + arrayLit(p(1, 1), p(1, 9), + intLit(1, p(1, 2)), + intLit(2, p(1, 5)), + intLit(3, p(1, 8))), + intLit(5, p(1, 11)), + ident("a", p(1, 15)), + p(1, 10), p(1, 16)))) + }) + + expectParse(t, "[1, 2, 3][a + 3 : b - 8]", func(p pfn) []Stmt { + return stmts( + exprStmt( + sliceExpr( + arrayLit(p(1, 1), p(1, 9), + intLit(1, p(1, 2)), + intLit(2, p(1, 5)), + intLit(3, p(1, 8))), + binaryExpr( + ident("a", p(1, 11)), + intLit(3, p(1, 15)), + token.Add, + p(1, 13)), + binaryExpr( + ident("b", p(1, 19)), + intLit(8, p(1, 23)), + token.Sub, + p(1, 21)), + p(1, 10), p(1, 24)))) + }) + + expectParse(t, `{a: 1, b: 2}["b"]`, func(p pfn) []Stmt { + return stmts( + exprStmt( + indexExpr( + mapLit(p(1, 1), p(1, 12), + mapElementLit( + "a", p(1, 2), p(1, 3), intLit(1, p(1, 5))), + mapElementLit( + "b", p(1, 8), p(1, 9), intLit(2, p(1, 11)))), + stringLit("b", p(1, 14)), + p(1, 13), p(1, 17)))) + }) + + expectParse(t, `{a: 1, b: 2}[a + b]`, func(p pfn) []Stmt { + return stmts( + exprStmt( + indexExpr( + mapLit(p(1, 1), p(1, 12), + mapElementLit( + "a", p(1, 2), p(1, 3), intLit(1, p(1, 5))), + mapElementLit( + "b", p(1, 8), p(1, 9), intLit(2, p(1, 11)))), + binaryExpr( + ident("a", p(1, 14)), + ident("b", p(1, 18)), + token.Add, + p(1, 16)), + p(1, 13), p(1, 19)))) + }) +} + +func TestParseLogical(t *testing.T) { + expectParse(t, "a && 5 || true", func(p pfn) []Stmt { + return stmts( + exprStmt( + binaryExpr( + binaryExpr( + ident("a", p(1, 1)), + intLit(5, p(1, 6)), + token.LAnd, + p(1, 3)), + boolLit(true, p(1, 11)), + token.LOr, + p(1, 8)))) + }) + + expectParse(t, "a || 5 && true", func(p pfn) []Stmt { + return stmts( + exprStmt( + binaryExpr( + ident("a", p(1, 1)), + binaryExpr( + intLit(5, p(1, 6)), + boolLit(true, p(1, 11)), + token.LAnd, + p(1, 8)), + token.LOr, + p(1, 3)))) + }) + + expectParse(t, "a && (5 || true)", func(p pfn) []Stmt { + return stmts( + exprStmt( + binaryExpr( + ident("a", p(1, 1)), + parenExpr( + binaryExpr( + intLit(5, p(1, 7)), + boolLit(true, p(1, 12)), + token.LOr, + p(1, 9)), + p(1, 6), p(1, 16)), + token.LAnd, + p(1, 3)))) + }) +} + +func TestParseMap(t *testing.T) { + expectParse(t, "{ key1: 1, key2: \"2\", key3: true }", func(p pfn) []Stmt { + return stmts( + exprStmt( + mapLit(p(1, 1), p(1, 34), + mapElementLit( + "key1", p(1, 3), p(1, 7), intLit(1, p(1, 9))), + mapElementLit( + "key2", p(1, 12), p(1, 16), stringLit("2", p(1, 18))), + mapElementLit( + "key3", p(1, 23), p(1, 27), boolLit(true, p(1, 29)))))) + }) + + expectParse(t, "{ \"key1\": 1 }", func(p pfn) []Stmt { + return stmts( + exprStmt( + mapLit(p(1, 1), p(1, 13), + mapElementLit( + "key1", p(1, 3), p(1, 9), intLit(1, p(1, 11)))))) + }) + + expectParse(t, "a = { key1: 1, key2: \"2\", key3: true }", + func(p pfn) []Stmt { + return stmts(assignStmt( + exprs(ident("a", p(1, 1))), + exprs(mapLit(p(1, 5), p(1, 38), + mapElementLit( + "key1", p(1, 7), p(1, 11), intLit(1, p(1, 13))), + mapElementLit( + "key2", p(1, 16), p(1, 20), stringLit("2", p(1, 22))), + mapElementLit( + "key3", p(1, 27), p(1, 31), boolLit(true, p(1, 33))))), + token.Assign, + p(1, 3))) + }) + + expectParse(t, "a = { key1: 1, key2: \"2\", key3: { k1: `bar`, k2: 4 } }", + func(p pfn) []Stmt { + return stmts(assignStmt( + exprs(ident("a", p(1, 1))), + exprs(mapLit(p(1, 5), p(1, 54), + mapElementLit( + "key1", p(1, 7), p(1, 11), intLit(1, p(1, 13))), + mapElementLit( + "key2", p(1, 16), p(1, 20), stringLit("2", p(1, 22))), + mapElementLit( + "key3", p(1, 27), p(1, 31), + mapLit(p(1, 33), p(1, 52), + mapElementLit( + "k1", p(1, 35), + p(1, 37), stringLit("bar", p(1, 39))), + mapElementLit( + "k2", p(1, 46), + p(1, 48), intLit(4, p(1, 50))))))), + token.Assign, + p(1, 3))) + }) + + expectParse(t, ` +{ + key1: 1, + key2: "2", + key3: true +}`, func(p pfn) []Stmt { + return stmts(exprStmt( + mapLit(p(2, 1), p(6, 1), + mapElementLit( + "key1", p(3, 2), p(3, 6), intLit(1, p(3, 8))), + mapElementLit( + "key2", p(4, 2), p(4, 6), stringLit("2", p(4, 8))), + mapElementLit( + "key3", p(5, 2), p(5, 6), boolLit(true, p(5, 8)))))) + }) + + expectParseError(t, ` +{ + key1: 1, + key2: "2", + key3: true, +}`) // unlike Go, trailing comma for the last element is illegal + + expectParseError(t, `{ key1: 1, }`) + expectParseError(t, `{ +key1: 1, +key2: 2, +}`) +} + +func TestParsePrecedence(t *testing.T) { + expectParseString(t, `a + b + c`, `((a + b) + c)`) + expectParseString(t, `a + b * c`, `(a + (b * c))`) + expectParseString(t, `x = 2 * 1 + 3 / 4`, `x = ((2 * 1) + (3 / 4))`) +} + +func TestParseSelector(t *testing.T) { + expectParse(t, "a.b", func(p pfn) []Stmt { + return stmts( + exprStmt( + selectorExpr( + ident("a", p(1, 1)), + stringLit("b", p(1, 3))))) + }) + + expectParse(t, "a.b.c", func(p pfn) []Stmt { + return stmts( + exprStmt( + selectorExpr( + selectorExpr( + ident("a", p(1, 1)), + stringLit("b", p(1, 3))), + stringLit("c", p(1, 5))))) + }) + + expectParse(t, "{k1:1}.k1", func(p pfn) []Stmt { + return stmts( + exprStmt( + selectorExpr( + mapLit( + p(1, 1), p(1, 6), + mapElementLit( + "k1", p(1, 2), p(1, 4), intLit(1, p(1, 5)))), + stringLit("k1", p(1, 8))))) + + }) + expectParse(t, "{k1:{v1:1}}.k1.v1", func(p pfn) []Stmt { + return stmts( + exprStmt( + selectorExpr( + selectorExpr( + mapLit( + p(1, 1), p(1, 11), + mapElementLit("k1", p(1, 2), p(1, 4), + mapLit(p(1, 5), p(1, 10), + mapElementLit( + "v1", p(1, 6), + p(1, 8), intLit(1, p(1, 9)))))), + stringLit("k1", p(1, 13))), + stringLit("v1", p(1, 16))))) + }) + + expectParse(t, "a.b = 4", func(p pfn) []Stmt { + return stmts( + assignStmt( + exprs( + selectorExpr( + ident("a", p(1, 1)), + stringLit("b", p(1, 3)))), + exprs(intLit(4, p(1, 7))), + token.Assign, p(1, 5))) + }) + + expectParse(t, "a.b.c = 4", func(p pfn) []Stmt { + return stmts( + assignStmt( + exprs( + selectorExpr( + selectorExpr( + ident("a", p(1, 1)), + stringLit("b", p(1, 3))), + stringLit("c", p(1, 5)))), + exprs(intLit(4, p(1, 9))), + token.Assign, p(1, 7))) + }) + + expectParse(t, "a.b.c = 4 + 5", func(p pfn) []Stmt { + return stmts( + assignStmt( + exprs( + selectorExpr( + selectorExpr( + ident("a", p(1, 1)), + stringLit("b", p(1, 3))), + stringLit("c", p(1, 5)))), + exprs( + binaryExpr( + intLit(4, p(1, 9)), + intLit(5, p(1, 13)), + token.Add, + p(1, 11))), + token.Assign, p(1, 7))) + }) + + expectParse(t, "a[0].c = 4", func(p pfn) []Stmt { + return stmts( + assignStmt( + exprs( + selectorExpr( + indexExpr( + ident("a", p(1, 1)), + intLit(0, p(1, 3)), + p(1, 2), p(1, 4)), + stringLit("c", p(1, 6)))), + exprs(intLit(4, p(1, 10))), + token.Assign, p(1, 8))) + }) + + expectParse(t, "a.b[0].c = 4", func(p pfn) []Stmt { + return stmts( + assignStmt( + exprs( + selectorExpr( + indexExpr( + selectorExpr( + ident("a", p(1, 1)), + stringLit("b", p(1, 3))), + intLit(0, p(1, 5)), + p(1, 4), p(1, 6)), + stringLit("c", p(1, 8)))), + exprs(intLit(4, p(1, 12))), + token.Assign, p(1, 10))) + }) + + expectParse(t, "a.b[0][2].c = 4", func(p pfn) []Stmt { + return stmts( + assignStmt( + exprs( + selectorExpr( + indexExpr( + indexExpr( + selectorExpr( + ident("a", p(1, 1)), + stringLit("b", p(1, 3))), + intLit(0, p(1, 5)), + p(1, 4), p(1, 6)), + intLit(2, p(1, 8)), + p(1, 7), p(1, 9)), + stringLit("c", p(1, 11)))), + exprs(intLit(4, p(1, 15))), + token.Assign, p(1, 13))) + }) + + expectParse(t, `a.b["key1"][2].c = 4`, func(p pfn) []Stmt { + return stmts( + assignStmt( + exprs( + selectorExpr( + indexExpr( + indexExpr( + selectorExpr( + ident("a", p(1, 1)), + stringLit("b", p(1, 3))), + stringLit("key1", p(1, 5)), + p(1, 4), p(1, 11)), + intLit(2, p(1, 13)), + p(1, 12), p(1, 14)), + stringLit("c", p(1, 16)))), + exprs(intLit(4, p(1, 20))), + token.Assign, p(1, 18))) + }) + + expectParse(t, "a[0].b[2].c = 4", func(p pfn) []Stmt { + return stmts( + assignStmt( + exprs( + selectorExpr( + indexExpr( + selectorExpr( + indexExpr( + ident("a", p(1, 1)), + intLit(0, p(1, 3)), + p(1, 2), p(1, 4)), + stringLit("b", p(1, 6))), + intLit(2, p(1, 8)), + p(1, 7), p(1, 9)), + stringLit("c", p(1, 11)))), + exprs(intLit(4, p(1, 15))), + token.Assign, p(1, 13))) + }) + + expectParseError(t, `a.(b.c)`) +} + +func TestParseSemicolon(t *testing.T) { + expectParse(t, "1", func(p pfn) []Stmt { + return stmts( + exprStmt(intLit(1, p(1, 1)))) + }) + + expectParse(t, "1;", func(p pfn) []Stmt { + return stmts( + exprStmt(intLit(1, p(1, 1)))) + }) + + expectParse(t, "1;;", func(p pfn) []Stmt { + return stmts( + exprStmt(intLit(1, p(1, 1))), + emptyStmt(false, p(1, 3))) + }) + + expectParse(t, `1 +`, func(p pfn) []Stmt { + return stmts( + exprStmt(intLit(1, p(1, 1)))) + }) + + expectParse(t, `1 +;`, func(p pfn) []Stmt { + return stmts( + exprStmt(intLit(1, p(1, 1))), + emptyStmt(false, p(2, 1))) + }) + + expectParse(t, `1; +;`, func(p pfn) []Stmt { + return stmts( + exprStmt(intLit(1, p(1, 1))), + emptyStmt(false, p(2, 1))) + }) +} + +func TestParseString(t *testing.T) { + expectParse(t, `a = "foo\nbar"`, func(p pfn) []Stmt { + return stmts( + assignStmt( + exprs(ident("a", p(1, 1))), + exprs(stringLit("foo\nbar", p(1, 5))), + token.Assign, + p(1, 3))) + }) + + expectParse(t, "a = `raw string`", func(p pfn) []Stmt { + return stmts( + assignStmt( + exprs(ident("a", p(1, 1))), + exprs(stringLit("raw string", p(1, 5))), + token.Assign, + p(1, 3))) + }) +} + +type pfn func(int, int) Pos // position conversion function +type expectedFn func(pos pfn) []Stmt // callback function to return expected results + +type parseTracer struct { + out []string +} + +func (o *parseTracer) Write(p []byte) (n int, err error) { + o.out = append(o.out, string(p)) + return len(p), nil +} + +//type slowPrinter struct { +//} +// +//func (o *slowPrinter) Write(p []byte) (n int, err error) { +// fmt.Print(string(p)) +// time.Sleep(25 * time.Millisecond) +// return len(p), nil +//} + +func expectParse(t *testing.T, input string, fn expectedFn) { + testFileSet := NewFileSet() + testFile := testFileSet.AddFile("test", -1, len(input)) + + var ok bool + defer func() { + if !ok { + // print trace + tr := &parseTracer{} + p := NewParser(testFile, []byte(input), tr) + actual, _ := p.ParseFile() + if actual != nil { + t.Logf("Parsed:\n%s", actual.String()) + } + t.Logf("Trace:\n%s", strings.Join(tr.out, "")) + } + }() + + p := NewParser(testFile, []byte(input), nil) + actual, err := p.ParseFile() + require.NoError(t, err) + + expected := fn(func(line, column int) Pos { + return Pos(int(testFile.LineStart(line)) + (column - 1)) + }) + require.Equal(t, len(expected), len(actual.Stmts)) + + for i := 0; i < len(expected); i++ { + equalStmt(t, expected[i], actual.Stmts[i]) + } + + ok = true +} + +func expectParseError(t *testing.T, input string) { + testFileSet := NewFileSet() + testFile := testFileSet.AddFile("test", -1, len(input)) + + var ok bool + defer func() { + if !ok { + // print trace + tr := &parseTracer{} + p := NewParser(testFile, []byte(input), tr) + _, _ = p.ParseFile() + t.Logf("Trace:\n%s", strings.Join(tr.out, "")) + } + }() + + p := NewParser(testFile, []byte(input), nil) + _, err := p.ParseFile() + require.Error(t, err) + ok = true +} + +func expectParseString(t *testing.T, input, expected string) { + var ok bool + defer func() { + if !ok { + // print trace + tr := &parseTracer{} + _, _ = parseSource("test", []byte(input), tr) + t.Logf("Trace:\n%s", strings.Join(tr.out, "")) + } + }() + + actual, err := parseSource("test", []byte(input), nil) + require.NoError(t, err) + require.Equal(t, expected, actual.String()) + ok = true +} + +func stmts(s ...Stmt) []Stmt { + return s +} + +func exprStmt(x Expr) *ExprStmt { + return &ExprStmt{Expr: x} +} + +func assignStmt( + lhs, rhs []Expr, + token token.Token, + pos Pos, +) *AssignStmt { + return &AssignStmt{LHS: lhs, RHS: rhs, Token: token, TokenPos: pos} +} + +func emptyStmt(implicit bool, pos Pos) *EmptyStmt { + return &EmptyStmt{Implicit: implicit, Semicolon: pos} +} + +func returnStmt(pos Pos, result Expr) *ReturnStmt { + return &ReturnStmt{Result: result, ReturnPos: pos} +} + +func forStmt( + init Stmt, + cond Expr, + post Stmt, + body *BlockStmt, + pos Pos, +) *ForStmt { + return &ForStmt{ + Cond: cond, Init: init, Post: post, Body: body, ForPos: pos, + } +} + +func forInStmt( + key, value *Ident, + seq Expr, + body *BlockStmt, + pos Pos, +) *ForInStmt { + return &ForInStmt{ + Key: key, Value: value, Iterable: seq, Body: body, ForPos: pos, + } +} + +func ifStmt( + init Stmt, + cond Expr, + body *BlockStmt, + elseStmt Stmt, + pos Pos, +) *IfStmt { + return &IfStmt{ + Init: init, Cond: cond, Body: body, Else: elseStmt, IfPos: pos, + } +} + +func incDecStmt( + expr Expr, + tok token.Token, + pos Pos, +) *IncDecStmt { + return &IncDecStmt{Expr: expr, Token: tok, TokenPos: pos} +} + +func funcType(params *IdentList, pos Pos) *FuncType { + return &FuncType{Params: params, FuncPos: pos} +} + +func blockStmt(lbrace, rbrace Pos, list ...Stmt) *BlockStmt { + return &BlockStmt{Stmts: list, LBrace: lbrace, RBrace: rbrace} +} + +func ident(name string, pos Pos) *Ident { + return &Ident{Name: name, NamePos: pos} +} + +func identList( + opening, closing Pos, + varArgs bool, + list ...*Ident, +) *IdentList { + return &IdentList{ + VarArgs: varArgs, List: list, LParen: opening, RParen: closing, + } +} + +func binaryExpr( + x, y Expr, + op token.Token, + pos Pos, +) *BinaryExpr { + return &BinaryExpr{LHS: x, RHS: y, Token: op, TokenPos: pos} +} + +func condExpr( + cond, trueExpr, falseExpr Expr, + questionPos, colonPos Pos, +) *CondExpr { + return &CondExpr{ + Cond: cond, True: trueExpr, False: falseExpr, + QuestionPos: questionPos, ColonPos: colonPos, + } +} + +func unaryExpr(x Expr, op token.Token, pos Pos) *UnaryExpr { + return &UnaryExpr{Expr: x, Token: op, TokenPos: pos} +} + +func importExpr(moduleName string, pos Pos) *ImportExpr { + return &ImportExpr{ + ModuleName: moduleName, Token: token.Import, TokenPos: pos, + } +} + +func exprs(list ...Expr) []Expr { + return list +} + +func intLit(value int64, pos Pos) *IntLit { + return &IntLit{Value: value, ValuePos: pos} +} + +func floatLit(value float64, pos Pos) *FloatLit { + return &FloatLit{Value: value, ValuePos: pos} +} + +func stringLit(value string, pos Pos) *StringLit { + return &StringLit{Value: value, ValuePos: pos} +} + +func charLit(value rune, pos Pos) *CharLit { + return &CharLit{ + Value: value, ValuePos: pos, Literal: fmt.Sprintf("'%c'", value), + } +} + +func boolLit(value bool, pos Pos) *BoolLit { + return &BoolLit{Value: value, ValuePos: pos} +} + +func arrayLit(lbracket, rbracket Pos, list ...Expr) *ArrayLit { + return &ArrayLit{LBrack: lbracket, RBrack: rbracket, Elements: list} +} + +func mapElementLit( + key string, + keyPos Pos, + colonPos Pos, + value Expr, +) *MapElementLit { + return &MapElementLit{ + Key: key, KeyPos: keyPos, ColonPos: colonPos, Value: value, + } +} + +func mapLit( + lbrace, rbrace Pos, + list ...*MapElementLit, +) *MapLit { + return &MapLit{LBrace: lbrace, RBrace: rbrace, Elements: list} +} + +func funcLit(funcType *FuncType, body *BlockStmt) *FuncLit { + return &FuncLit{Type: funcType, Body: body} +} + +func parenExpr(x Expr, lparen, rparen Pos) *ParenExpr { + return &ParenExpr{Expr: x, LParen: lparen, RParen: rparen} +} + +func callExpr( + f Expr, + lparen, rparen Pos, + args ...Expr, +) *CallExpr { + return &CallExpr{Func: f, LParen: lparen, RParen: rparen, Args: args} +} + +func indexExpr( + x, index Expr, + lbrack, rbrack Pos, +) *IndexExpr { + return &IndexExpr{ + Expr: x, Index: index, LBrack: lbrack, RBrack: rbrack, + } +} + +func sliceExpr( + x, low, high Expr, + lbrack, rbrack Pos, +) *SliceExpr { + return &SliceExpr{ + Expr: x, Low: low, High: high, LBrack: lbrack, RBrack: rbrack, + } +} + +func errorExpr( + pos Pos, + x Expr, + lparen, rparen Pos, +) *ErrorExpr { + return &ErrorExpr{ + Expr: x, ErrorPos: pos, LParen: lparen, RParen: rparen, + } +} + +func selectorExpr(x, sel Expr) *SelectorExpr { + return &SelectorExpr{Expr: x, Sel: sel} +} + +func equalStmt(t *testing.T, expected, actual Stmt) { + if expected == nil || reflect.ValueOf(expected).IsNil() { + require.Nil(t, actual, "expected nil, but got not nil") + return + } + require.NotNil(t, actual, "expected not nil, but got nil") + require.IsType(t, expected, actual) + + switch expected := expected.(type) { + case *ExprStmt: + equalExpr(t, expected.Expr, actual.(*ExprStmt).Expr) + case *EmptyStmt: + require.Equal(t, expected.Implicit, + actual.(*EmptyStmt).Implicit) + require.Equal(t, expected.Semicolon, + actual.(*EmptyStmt).Semicolon) + case *BlockStmt: + require.Equal(t, expected.LBrace, + actual.(*BlockStmt).LBrace) + require.Equal(t, expected.RBrace, + actual.(*BlockStmt).RBrace) + equalStmts(t, expected.Stmts, + actual.(*BlockStmt).Stmts) + case *AssignStmt: + equalExprs(t, expected.LHS, + actual.(*AssignStmt).LHS) + equalExprs(t, expected.RHS, + actual.(*AssignStmt).RHS) + require.Equal(t, int(expected.Token), + int(actual.(*AssignStmt).Token)) + require.Equal(t, int(expected.TokenPos), + int(actual.(*AssignStmt).TokenPos)) + case *IfStmt: + equalStmt(t, expected.Init, actual.(*IfStmt).Init) + equalExpr(t, expected.Cond, actual.(*IfStmt).Cond) + equalStmt(t, expected.Body, actual.(*IfStmt).Body) + equalStmt(t, expected.Else, actual.(*IfStmt).Else) + require.Equal(t, expected.IfPos, actual.(*IfStmt).IfPos) + case *IncDecStmt: + equalExpr(t, expected.Expr, + actual.(*IncDecStmt).Expr) + require.Equal(t, expected.Token, + actual.(*IncDecStmt).Token) + require.Equal(t, expected.TokenPos, + actual.(*IncDecStmt).TokenPos) + case *ForStmt: + equalStmt(t, expected.Init, actual.(*ForStmt).Init) + equalExpr(t, expected.Cond, actual.(*ForStmt).Cond) + equalStmt(t, expected.Post, actual.(*ForStmt).Post) + equalStmt(t, expected.Body, actual.(*ForStmt).Body) + require.Equal(t, expected.ForPos, actual.(*ForStmt).ForPos) + case *ForInStmt: + equalExpr(t, expected.Key, + actual.(*ForInStmt).Key) + equalExpr(t, expected.Value, + actual.(*ForInStmt).Value) + equalExpr(t, expected.Iterable, + actual.(*ForInStmt).Iterable) + equalStmt(t, expected.Body, + actual.(*ForInStmt).Body) + require.Equal(t, expected.ForPos, + actual.(*ForInStmt).ForPos) + case *ReturnStmt: + equalExpr(t, expected.Result, + actual.(*ReturnStmt).Result) + require.Equal(t, expected.ReturnPos, + actual.(*ReturnStmt).ReturnPos) + case *BranchStmt: + equalExpr(t, expected.Label, + actual.(*BranchStmt).Label) + require.Equal(t, expected.Token, + actual.(*BranchStmt).Token) + require.Equal(t, expected.TokenPos, + actual.(*BranchStmt).TokenPos) + default: + panic(fmt.Errorf("unknown type: %T", expected)) + } +} + +func equalExpr(t *testing.T, expected, actual Expr) { + if expected == nil || reflect.ValueOf(expected).IsNil() { + require.Nil(t, actual, "expected nil, but got not nil") + return + } + require.NotNil(t, actual, "expected not nil, but got nil") + require.IsType(t, expected, actual) + + switch expected := expected.(type) { + case *Ident: + require.Equal(t, expected.Name, + actual.(*Ident).Name) + require.Equal(t, int(expected.NamePos), + int(actual.(*Ident).NamePos)) + case *IntLit: + require.Equal(t, expected.Value, + actual.(*IntLit).Value) + require.Equal(t, int(expected.ValuePos), + int(actual.(*IntLit).ValuePos)) + case *FloatLit: + require.Equal(t, expected.Value, + actual.(*FloatLit).Value) + require.Equal(t, int(expected.ValuePos), + int(actual.(*FloatLit).ValuePos)) + case *BoolLit: + require.Equal(t, expected.Value, + actual.(*BoolLit).Value) + require.Equal(t, int(expected.ValuePos), + int(actual.(*BoolLit).ValuePos)) + case *CharLit: + require.Equal(t, expected.Value, + actual.(*CharLit).Value) + require.Equal(t, int(expected.ValuePos), + int(actual.(*CharLit).ValuePos)) + case *StringLit: + require.Equal(t, expected.Value, + actual.(*StringLit).Value) + require.Equal(t, int(expected.ValuePos), + int(actual.(*StringLit).ValuePos)) + case *ArrayLit: + require.Equal(t, expected.LBrack, + actual.(*ArrayLit).LBrack) + require.Equal(t, expected.RBrack, + actual.(*ArrayLit).RBrack) + equalExprs(t, expected.Elements, + actual.(*ArrayLit).Elements) + case *MapLit: + require.Equal(t, expected.LBrace, + actual.(*MapLit).LBrace) + require.Equal(t, expected.RBrace, + actual.(*MapLit).RBrace) + equalMapElements(t, expected.Elements, + actual.(*MapLit).Elements) + case *BinaryExpr: + equalExpr(t, expected.LHS, + actual.(*BinaryExpr).LHS) + equalExpr(t, expected.RHS, + actual.(*BinaryExpr).RHS) + require.Equal(t, expected.Token, + actual.(*BinaryExpr).Token) + require.Equal(t, expected.TokenPos, + actual.(*BinaryExpr).TokenPos) + case *UnaryExpr: + equalExpr(t, expected.Expr, + actual.(*UnaryExpr).Expr) + require.Equal(t, expected.Token, + actual.(*UnaryExpr).Token) + require.Equal(t, expected.TokenPos, + actual.(*UnaryExpr).TokenPos) + case *FuncLit: + equalFuncType(t, expected.Type, + actual.(*FuncLit).Type) + equalStmt(t, expected.Body, + actual.(*FuncLit).Body) + case *CallExpr: + equalExpr(t, expected.Func, + actual.(*CallExpr).Func) + require.Equal(t, expected.LParen, + actual.(*CallExpr).LParen) + require.Equal(t, expected.RParen, + actual.(*CallExpr).RParen) + equalExprs(t, expected.Args, + actual.(*CallExpr).Args) + case *ParenExpr: + equalExpr(t, expected.Expr, + actual.(*ParenExpr).Expr) + require.Equal(t, expected.LParen, + actual.(*ParenExpr).LParen) + require.Equal(t, expected.RParen, + actual.(*ParenExpr).RParen) + case *IndexExpr: + equalExpr(t, expected.Expr, + actual.(*IndexExpr).Expr) + equalExpr(t, expected.Index, + actual.(*IndexExpr).Index) + require.Equal(t, expected.LBrack, + actual.(*IndexExpr).LBrack) + require.Equal(t, expected.RBrack, + actual.(*IndexExpr).RBrack) + case *SliceExpr: + equalExpr(t, expected.Expr, + actual.(*SliceExpr).Expr) + equalExpr(t, expected.Low, + actual.(*SliceExpr).Low) + equalExpr(t, expected.High, + actual.(*SliceExpr).High) + require.Equal(t, expected.LBrack, + actual.(*SliceExpr).LBrack) + require.Equal(t, expected.RBrack, + actual.(*SliceExpr).RBrack) + case *SelectorExpr: + equalExpr(t, expected.Expr, + actual.(*SelectorExpr).Expr) + equalExpr(t, expected.Sel, + actual.(*SelectorExpr).Sel) + case *ImportExpr: + require.Equal(t, expected.ModuleName, + actual.(*ImportExpr).ModuleName) + require.Equal(t, int(expected.TokenPos), + int(actual.(*ImportExpr).TokenPos)) + require.Equal(t, expected.Token, + actual.(*ImportExpr).Token) + case *ErrorExpr: + equalExpr(t, expected.Expr, + actual.(*ErrorExpr).Expr) + require.Equal(t, int(expected.ErrorPos), + int(actual.(*ErrorExpr).ErrorPos)) + require.Equal(t, int(expected.LParen), + int(actual.(*ErrorExpr).LParen)) + require.Equal(t, int(expected.RParen), + int(actual.(*ErrorExpr).RParen)) + case *CondExpr: + equalExpr(t, expected.Cond, + actual.(*CondExpr).Cond) + equalExpr(t, expected.True, + actual.(*CondExpr).True) + equalExpr(t, expected.False, + actual.(*CondExpr).False) + require.Equal(t, expected.QuestionPos, + actual.(*CondExpr).QuestionPos) + require.Equal(t, expected.ColonPos, + actual.(*CondExpr).ColonPos) + default: + panic(fmt.Errorf("unknown type: %T", expected)) + } +} + +func equalFuncType(t *testing.T, expected, actual *FuncType) { + require.Equal(t, expected.Params.LParen, actual.Params.LParen) + require.Equal(t, expected.Params.RParen, actual.Params.RParen) + equalIdents(t, expected.Params.List, actual.Params.List) +} + +func equalIdents(t *testing.T, expected, actual []*Ident) { + require.Equal(t, len(expected), len(actual)) + for i := 0; i < len(expected); i++ { + equalExpr(t, expected[i], actual[i]) + } +} + +func equalExprs(t *testing.T, expected, actual []Expr) { + require.Equal(t, len(expected), len(actual)) + for i := 0; i < len(expected); i++ { + equalExpr(t, expected[i], actual[i]) + } +} + +func equalStmts(t *testing.T, expected, actual []Stmt) { + require.Equal(t, len(expected), len(actual)) + for i := 0; i < len(expected); i++ { + equalStmt(t, expected[i], actual[i]) + } +} + +func equalMapElements( + t *testing.T, + expected, actual []*MapElementLit, +) { + require.Equal(t, len(expected), len(actual)) + for i := 0; i < len(expected); i++ { + require.Equal(t, expected[i].Key, actual[i].Key) + require.Equal(t, expected[i].KeyPos, actual[i].KeyPos) + require.Equal(t, expected[i].ColonPos, actual[i].ColonPos) + equalExpr(t, expected[i].Value, actual[i].Value) + } +} + +func parseSource( + filename string, + src []byte, + trace io.Writer, +) (res *File, err error) { + fileSet := NewFileSet() + file := fileSet.AddFile(filename, -1, len(src)) + + p := NewParser(file, src, trace) + return p.ParseFile() +} diff --git a/compiler/source/pos.go b/internal/pos.go similarity index 92% rename from compiler/source/pos.go rename to internal/pos.go index 72128b1..280ee36 100644 --- a/compiler/source/pos.go +++ b/internal/pos.go @@ -1,4 +1,4 @@ -package source +package internal // Pos represents a position in the file set. type Pos int diff --git a/internal/require/require.go b/internal/require/require.go new file mode 100644 index 0000000..f42c1af --- /dev/null +++ b/internal/require/require.go @@ -0,0 +1,378 @@ +package require + +import ( + "bytes" + "fmt" + "reflect" + "runtime" + "strings" + "testing" + "unicode" + "unicode/utf8" + + "github.com/d5/tengo" + "github.com/d5/tengo/internal" + "github.com/d5/tengo/internal/token" +) + +// NoError asserts err is not an error. +func NoError(t *testing.T, err error, msg ...interface{}) { + if err != nil { + failExpectedActual(t, "no error", err, msg...) + } +} + +// Error asserts err is an error. +func Error(t *testing.T, err error, msg ...interface{}) { + if err == nil { + failExpectedActual(t, "error", err, msg...) + } +} + +// Nil asserts v is nil. +func Nil(t *testing.T, v interface{}, msg ...interface{}) { + if !isNil(v) { + failExpectedActual(t, "nil", v, msg...) + } +} + +// True asserts v is true. +func True(t *testing.T, v bool, msg ...interface{}) { + if !v { + failExpectedActual(t, "true", v, msg...) + } +} + +// False asserts vis false. +func False(t *testing.T, v bool, msg ...interface{}) { + if v { + failExpectedActual(t, "false", v, msg...) + } +} + +// NotNil asserts v is not nil. +func NotNil(t *testing.T, v interface{}, msg ...interface{}) { + if isNil(v) { + failExpectedActual(t, "not nil", v, msg...) + } +} + +// IsType asserts expected and actual are of the same type. +func IsType( + t *testing.T, + expected, actual interface{}, + msg ...interface{}, +) { + if reflect.TypeOf(expected) != reflect.TypeOf(actual) { + failExpectedActual(t, reflect.TypeOf(expected), + reflect.TypeOf(actual), msg...) + } +} + +// Equal asserts expected and actual are equal. +func Equal( + t *testing.T, + expected, actual interface{}, + msg ...interface{}, +) { + if isNil(expected) { + Nil(t, actual, "expected nil, but got not nil") + return + } + NotNil(t, actual, "expected not nil, but got nil") + IsType(t, expected, actual, msg...) + + switch expected := expected.(type) { + case int: + if expected != actual.(int) { + failExpectedActual(t, expected, actual, msg...) + } + case int64: + if expected != actual.(int64) { + failExpectedActual(t, expected, actual, msg...) + } + case float64: + if expected != actual.(float64) { + failExpectedActual(t, expected, actual, msg...) + } + case string: + if expected != actual.(string) { + failExpectedActual(t, expected, actual, msg...) + } + case []byte: + if !bytes.Equal(expected, actual.([]byte)) { + failExpectedActual(t, string(expected), + string(actual.([]byte)), msg...) + } + case []string: + if !equalStringSlice(expected, actual.([]string)) { + failExpectedActual(t, expected, actual, msg...) + } + case []int: + if !equalIntSlice(expected, actual.([]int)) { + failExpectedActual(t, expected, actual, msg...) + } + case bool: + if expected != actual.(bool) { + failExpectedActual(t, expected, actual, msg...) + } + case rune: + if expected != actual.(rune) { + failExpectedActual(t, expected, actual, msg...) + } + case *internal.Symbol: + if !equalSymbol(expected, actual.(*internal.Symbol)) { + failExpectedActual(t, expected, actual, msg...) + } + case internal.Pos: + if expected != actual.(internal.Pos) { + failExpectedActual(t, expected, actual, msg...) + } + case token.Token: + if expected != actual.(token.Token) { + failExpectedActual(t, expected, actual, msg...) + } + case []tengo.Object: + equalObjectSlice(t, expected, actual.([]tengo.Object), msg...) + case *tengo.Int: + Equal(t, expected.Value, actual.(*tengo.Int).Value, msg...) + case *tengo.Float: + Equal(t, expected.Value, actual.(*tengo.Float).Value, msg...) + case *tengo.String: + Equal(t, expected.Value, actual.(*tengo.String).Value, msg...) + case *tengo.Char: + Equal(t, expected.Value, actual.(*tengo.Char).Value, msg...) + case *tengo.Bool: + if expected != actual { + failExpectedActual(t, expected, actual, msg...) + } + case *tengo.Array: + equalObjectSlice(t, expected.Value, + actual.(*tengo.Array).Value, msg...) + case *tengo.ImmutableArray: + equalObjectSlice(t, expected.Value, + actual.(*tengo.ImmutableArray).Value, msg...) + case *tengo.Bytes: + if !bytes.Equal(expected.Value, actual.(*tengo.Bytes).Value) { + failExpectedActual(t, string(expected.Value), + string(actual.(*tengo.Bytes).Value), msg...) + } + case *tengo.Map: + equalObjectMap(t, expected.Value, + actual.(*tengo.Map).Value, msg...) + case *tengo.ImmutableMap: + equalObjectMap(t, expected.Value, + actual.(*tengo.ImmutableMap).Value, msg...) + case *tengo.CompiledFunction: + equalCompiledFunction(t, expected, + actual.(*tengo.CompiledFunction), msg...) + case *tengo.Undefined: + if expected != actual { + failExpectedActual(t, expected, actual, msg...) + } + case *tengo.Error: + Equal(t, expected.Value, actual.(*tengo.Error).Value, msg...) + case tengo.Object: + if !expected.Equals(actual.(tengo.Object)) { + failExpectedActual(t, expected, actual, msg...) + } + case *internal.SourceFileSet: + equalFileSet(t, expected, actual.(*internal.SourceFileSet), msg...) + case *internal.SourceFile: + Equal(t, expected.Name, actual.(*internal.SourceFile).Name, msg...) + Equal(t, expected.Base, actual.(*internal.SourceFile).Base, msg...) + Equal(t, expected.Size, actual.(*internal.SourceFile).Size, msg...) + True(t, equalIntSlice(expected.Lines, + actual.(*internal.SourceFile).Lines), msg...) + case error: + if expected != actual.(error) { + failExpectedActual(t, expected, actual, msg...) + } + default: + panic(fmt.Errorf("type not implemented: %T", expected)) + } +} + +// Fail marks the function as having failed but continues execution. +func Fail(t *testing.T, msg ...interface{}) { + t.Logf("\nError trace:\n\t%s\n%s", strings.Join(errorTrace(), "\n\t"), + message(msg...)) + t.Fail() +} + +func failExpectedActual( + t *testing.T, + expected, actual interface{}, + msg ...interface{}, +) { + var addMsg string + if len(msg) > 0 { + addMsg = "\nMessage: " + message(msg...) + } + + t.Logf("\nError trace:\n\t%s\nExpected: %v\nActual: %v%s", + strings.Join(errorTrace(), "\n\t"), + expected, actual, + addMsg) + t.FailNow() +} + +func message(formatArgs ...interface{}) string { + var format string + var args []interface{} + if len(formatArgs) > 0 { + format = formatArgs[0].(string) + } + if len(formatArgs) > 1 { + args = formatArgs[1:] + } + return fmt.Sprintf(format, args...) +} + +func equalIntSlice(a, b []int) bool { + if len(a) != len(b) { + return false + } + for i := 0; i < len(a); i++ { + if a[i] != b[i] { + return false + } + } + return true +} + +func equalStringSlice(a, b []string) bool { + if len(a) != len(b) { + return false + } + for i := 0; i < len(a); i++ { + if a[i] != b[i] { + return false + } + } + return true +} + +func equalSymbol(a, b *internal.Symbol) bool { + return a.Name == b.Name && + a.Index == b.Index && + a.Scope == b.Scope +} + +func equalObjectSlice( + t *testing.T, + expected, actual []tengo.Object, + msg ...interface{}, +) { + Equal(t, len(expected), len(actual), msg...) + for i := 0; i < len(expected); i++ { + Equal(t, expected[i], actual[i], msg...) + } +} + +func equalFileSet( + t *testing.T, + expected, actual *internal.SourceFileSet, + msg ...interface{}, +) { + Equal(t, len(expected.Files), len(actual.Files), msg...) + for i, f := range expected.Files { + Equal(t, f, actual.Files[i], msg...) + } + Equal(t, expected.Base, actual.Base) + Equal(t, expected.LastFile, actual.LastFile) +} + +func equalObjectMap( + t *testing.T, + expected, actual map[string]tengo.Object, + msg ...interface{}, +) { + Equal(t, len(expected), len(actual), msg...) + for key, expectedVal := range expected { + actualVal := actual[key] + Equal(t, expectedVal, actualVal, msg...) + } +} + +func equalCompiledFunction( + t *testing.T, + expected, actual tengo.Object, + msg ...interface{}, +) { + expectedT := expected.(*tengo.CompiledFunction) + actualT := actual.(*tengo.CompiledFunction) + Equal(t, + internal.FormatInstructions(expectedT.Instructions, 0), + internal.FormatInstructions(actualT.Instructions, 0), msg...) +} + +func isNil(v interface{}) bool { + if v == nil { + return true + } + value := reflect.ValueOf(v) + kind := value.Kind() + return kind >= reflect.Chan && kind <= reflect.Slice && value.IsNil() +} + +func errorTrace() []string { + var pc uintptr + file := "" + line := 0 + var ok bool + name := "" + + var callers []string + for i := 0; ; i++ { + pc, file, line, ok = runtime.Caller(i) + if !ok { + break + } + + if file == "" { + break + } + + f := runtime.FuncForPC(pc) + if f == nil { + break + } + name = f.Name() + + if name == "testing.tRunner" { + break + } + + parts := strings.Split(file, "/") + file = parts[len(parts)-1] + if len(parts) > 1 { + dir := parts[len(parts)-2] + if dir != "require" || + file == "mock_test.go" { + callers = append(callers, fmt.Sprintf("%s:%d", file, line)) + } + } + + // Drop the package + segments := strings.Split(name, ".") + name = segments[len(segments)-1] + if isTest(name, "Test") || + isTest(name, "Benchmark") || + isTest(name, "Example") { + break + } + } + return callers +} + +func isTest(name, prefix string) bool { + if !strings.HasPrefix(name, prefix) { + return false + } + if len(name) == len(prefix) { // "Test" is ok + return true + } + r, _ := utf8.DecodeRuneInString(name[len(prefix):]) + return !unicode.IsLower(r) +} diff --git a/compiler/scanner/scanner.go b/internal/scanner.go similarity index 83% rename from compiler/scanner/scanner.go rename to internal/scanner.go index 387cd8e..555755f 100644 --- a/compiler/scanner/scanner.go +++ b/internal/scanner.go @@ -1,45 +1,53 @@ -/* - Scanner reads the Tengo source text and tokenize them. - - Scanner is a modified version of Go's scanner implementation. - - Copyright 2009 The Go Authors. All rights reserved. - Use of this source code is governed by a BSD-style - license that can be found in the LICENSE file. -*/ - -package scanner +package internal import ( "fmt" "unicode" "unicode/utf8" - "github.com/d5/tengo/compiler/source" - "github.com/d5/tengo/compiler/token" + "github.com/d5/tengo/internal/token" ) // byte order mark const bom = 0xFEFF -// Scanner reads the Tengo source text. +// ScanMode represents a scanner mode. +type ScanMode int + +// List of scanner modes. +const ( + ScanComments ScanMode = 1 << iota + DontInsertSemis +) + +// ScannerErrorHandler is an error handler for the scanner. +type ScannerErrorHandler func(pos SourceFilePos, msg string) + +// Scanner reads the Tengo source text. It's based on Go's scanner +// implementation. type Scanner struct { - file *source.File // source file handle - src []byte // source - ch rune // current character - offset int // character offset - readOffset int // reading offset (position after current character) - lineOffset int // current line offset - insertSemi bool // insert a semicolon before next newline - errorHandler ErrorHandler // error reporting; or nil - errorCount int // number of errors encountered - mode Mode + file *SourceFile // source file handle + src []byte // source + ch rune // current character + offset int // character offset + readOffset int // reading offset (position after current character) + lineOffset int // current line offset + insertSemi bool // insert a semicolon before next newline + errorHandler ScannerErrorHandler // error reporting; or nil + errorCount int // number of errors encountered + mode ScanMode } // NewScanner creates a Scanner. -func NewScanner(file *source.File, src []byte, errorHandler ErrorHandler, mode Mode) *Scanner { +func NewScanner( + file *SourceFile, + src []byte, + errorHandler ScannerErrorHandler, + mode ScanMode, +) *Scanner { if file.Size != len(src) { - panic(fmt.Sprintf("file size (%d) does not match src len (%d)", file.Size, len(src))) + panic(fmt.Sprintf("file size (%d) does not match src len (%d)", + file.Size, len(src))) } s := &Scanner{ @@ -64,7 +72,11 @@ func (s *Scanner) ErrorCount() int { } // Scan returns a token, token literal and its position. -func (s *Scanner) Scan() (tok token.Token, literal string, pos source.Pos) { +func (s *Scanner) Scan() ( + tok token.Token, + literal string, + pos Pos, +) { s.skipWhitespace() pos = s.file.FileSetPos(s.offset) @@ -77,7 +89,8 @@ func (s *Scanner) Scan() (tok token.Token, literal string, pos source.Pos) { literal = s.scanIdentifier() tok = token.Lookup(literal) switch tok { - case token.Ident, token.Break, token.Continue, token.Return, token.Export, token.True, token.False, token.Undefined: + case token.Ident, token.Break, token.Continue, token.Return, + token.Export, token.True, token.False, token.Undefined: insertSemi = true } case '0' <= ch && ch <= '9': @@ -184,9 +197,11 @@ func (s *Scanner) Scan() (tok token.Token, literal string, pos source.Pos) { case '^': tok = s.switch2(token.Xor, token.XorAssign) case '<': - tok = s.switch4(token.Less, token.LessEq, '<', token.Shl, token.ShlAssign) + tok = s.switch4(token.Less, token.LessEq, '<', + token.Shl, token.ShlAssign) case '>': - tok = s.switch4(token.Greater, token.GreaterEq, '>', token.Shr, token.ShrAssign) + tok = s.switch4(token.Greater, token.GreaterEq, '>', + token.Shr, token.ShrAssign) case '=': tok = s.switch2(token.Assign, token.Equal) case '!': @@ -203,18 +218,17 @@ func (s *Scanner) Scan() (tok token.Token, literal string, pos source.Pos) { default: // next reports unexpected BOMs - don't repeat if ch != bom { - s.error(s.file.Offset(pos), fmt.Sprintf("illegal character %#U", ch)) + s.error(s.file.Offset(pos), + fmt.Sprintf("illegal character %#U", ch)) } insertSemi = s.insertSemi // preserve insertSemi info tok = token.Illegal literal = string(ch) } } - if s.mode&DontInsertSemis == 0 { s.insertSemi = insertSemi } - return } @@ -254,7 +268,6 @@ func (s *Scanner) peek() byte { if s.readOffset < len(s.src) { return s.src[s.readOffset] } - return 0 } @@ -262,7 +275,6 @@ func (s *Scanner) error(offset int, msg string) { if s.errorHandler != nil { s.errorHandler(s.file.Position(s.file.FileSetPos(offset)), msg) } - s.errorCount++ } @@ -310,11 +322,9 @@ exit: lit = lit[:len(lit)-1] numCR-- } - if numCR > 0 { lit = StripCR(lit, lit[1] == '*') } - return string(lit) } @@ -358,7 +368,6 @@ func (s *Scanner) findLineEnd() bool { } s.next() // consume '/' } - return false } @@ -367,7 +376,6 @@ func (s *Scanner) scanIdentifier() string { for isLetter(s.ch) || isDigit(s.ch) { s.next() } - return string(s.src[offs:s.offset]) } @@ -377,7 +385,9 @@ func (s *Scanner) scanMantissa(base int) { } } -func (s *Scanner) scanNumber(seenDecimalPoint bool) (tok token.Token, lit string) { +func (s *Scanner) scanNumber( + seenDecimalPoint bool, +) (tok token.Token, lit string) { // digitVal(s.ch) < 10 offs := s.offset tok = token.Int @@ -422,7 +432,6 @@ func (s *Scanner) scanNumber(seenDecimalPoint bool) (tok token.Token, lit string s.error(offs, "illegal octal number") } } - return } @@ -449,7 +458,6 @@ exponent: s.error(offs, "illegal floating-point exponent") } } - return } @@ -486,7 +494,8 @@ func (s *Scanner) scanEscape(quote rune) bool { for n > 0 { d := uint32(digitVal(s.ch)) if d >= base { - msg := fmt.Sprintf("illegal character %#U in escape sequence", s.ch) + msg := fmt.Sprintf( + "illegal character %#U in escape sequence", s.ch) if s.ch < 0 { msg = "escape sequence not terminated" } @@ -502,7 +511,6 @@ func (s *Scanner) scanEscape(quote rune) bool { s.error(offs, "escape sequence is invalid Unicode code point") return false } - return true } @@ -537,7 +545,6 @@ func (s *Scanner) scanRune() string { if valid && n != 1 { s.error(offs, "illegal rune literal") } - return string(s.src[offs:s.offset]) } @@ -558,7 +565,6 @@ func (s *Scanner) scanString() string { s.scanEscape('"') } } - return string(s.src[offs:s.offset]) } @@ -588,30 +594,30 @@ func (s *Scanner) scanRawString() string { if hasCR { lit = StripCR(lit, false) } - return string(lit) } // StripCR removes carriage return characters. func StripCR(b []byte, comment bool) []byte { c := make([]byte, len(b)) - i := 0 for j, ch := range b { - // In a /*-style comment, don't strip \r from *\r/ (incl. sequences of \r from *\r\r...\r/) - // since the resulting */ would terminate the comment too early unless the \r is immediately - // following the opening /* in which case it's ok because /*/ is not closed yet. - if ch != '\r' || comment && i > len("/*") && c[i-1] == '*' && j+1 < len(b) && b[j+1] == '/' { + // In a /*-style comment, don't strip \r from *\r/ (incl. sequences of + // \r from *\r\r...\r/) since the resulting */ would terminate the + // comment too early unless the \r is immediately following the opening + // /* in which case it's ok because /*/ is not closed yet. + if ch != '\r' || comment && i > len("/*") && c[i-1] == '*' && + j+1 < len(b) && b[j+1] == '/' { c[i] = ch i++ } } - return c[:i] } func (s *Scanner) skipWhitespace() { - for s.ch == ' ' || s.ch == '\t' || s.ch == '\n' && !s.insertSemi || s.ch == '\r' { + for s.ch == ' ' || s.ch == '\t' || s.ch == '\n' && !s.insertSemi || + s.ch == '\r' { s.next() } } @@ -621,49 +627,53 @@ func (s *Scanner) switch2(tok0, tok1 token.Token) token.Token { s.next() return tok1 } - return tok0 } -func (s *Scanner) switch3(tok0, tok1 token.Token, ch2 rune, tok2 token.Token) token.Token { +func (s *Scanner) switch3( + tok0, tok1 token.Token, + ch2 rune, + tok2 token.Token, +) token.Token { if s.ch == '=' { s.next() return tok1 } - if s.ch == ch2 { s.next() return tok2 } - return tok0 } -func (s *Scanner) switch4(tok0, tok1 token.Token, ch2 rune, tok2, tok3 token.Token) token.Token { +func (s *Scanner) switch4( + tok0, tok1 token.Token, + ch2 rune, + tok2, tok3 token.Token, +) token.Token { if s.ch == '=' { s.next() return tok1 } - if s.ch == ch2 { s.next() if s.ch == '=' { s.next() return tok3 } - return tok2 } - return tok0 } func isLetter(ch rune) bool { - return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= utf8.RuneSelf && unicode.IsLetter(ch) + return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || + ch >= utf8.RuneSelf && unicode.IsLetter(ch) } func isDigit(ch rune) bool { - return '0' <= ch && ch <= '9' || ch >= utf8.RuneSelf && unicode.IsDigit(ch) + return '0' <= ch && ch <= '9' || + ch >= utf8.RuneSelf && unicode.IsDigit(ch) } func digitVal(ch rune) int { @@ -675,6 +685,5 @@ func digitVal(ch rune) int { case 'A' <= ch && ch <= 'F': return int(ch - 'A' + 10) } - return 16 // larger than any legal digit val } diff --git a/compiler/scanner/scanner_test.go b/internal/scanner_test.go similarity index 80% rename from compiler/scanner/scanner_test.go rename to internal/scanner_test.go index d0e495c..88043ac 100644 --- a/compiler/scanner/scanner_test.go +++ b/internal/scanner_test.go @@ -1,4 +1,4 @@ -package scanner_test +package internal_test import ( "fmt" @@ -7,13 +7,12 @@ import ( "testing" "time" - "github.com/d5/tengo/assert" - "github.com/d5/tengo/compiler/scanner" - "github.com/d5/tengo/compiler/source" - "github.com/d5/tengo/compiler/token" + "github.com/d5/tengo/internal" + "github.com/d5/tengo/internal/require" + "github.com/d5/tengo/internal/token" ) -var testFileSet = source.NewFileSet() +var testFileSet = internal.NewFileSet() type scanResult struct { Token token.Token @@ -152,7 +151,8 @@ func TestScanner_Scan(t *testing.T) { switch tc.token { case token.Comment: // strip CRs in comments - expectedLiteral = string(scanner.StripCR([]byte(tc.literal), tc.literal[1] == '*')) + expectedLiteral = string(internal.StripCR([]byte(tc.literal), + tc.literal[1] == '*')) //-style comment literal doesn't contain newline if expectedLiteral[1] == '/' { @@ -167,7 +167,8 @@ func TestScanner_Scan(t *testing.T) { // strip CRs in raw string expectedLiteral = tc.literal if expectedLiteral[0] == '`' { - expectedLiteral = string(scanner.StripCR([]byte(expectedLiteral), false)) + expectedLiteral = string(internal.StripCR( + []byte(expectedLiteral), false)) } } else if tc.token.IsKeyword() { expectedLiteral = tc.literal @@ -187,8 +188,10 @@ func TestScanner_Scan(t *testing.T) { } } - scanExpect(t, strings.Join(lines, "\n"), scanner.ScanComments|scanner.DontInsertSemis, expected...) - scanExpect(t, strings.Join(lines, "\n"), scanner.DontInsertSemis, expectedSkipComments...) + scanExpect(t, strings.Join(lines, "\n"), + internal.ScanComments|internal.DontInsertSemis, expected...) + scanExpect(t, strings.Join(lines, "\n"), + internal.DontInsertSemis, expectedSkipComments...) } func TestStripCR(t *testing.T) { @@ -207,18 +210,24 @@ func TestStripCR(t *testing.T) { {"/*\r/\r*\r/*/", "/*/*\r/*/"}, {"/*\r\r\r\r*/", "/**/"}, } { - actual := string(scanner.StripCR([]byte(tc.input), len(tc.input) >= 2 && tc.input[1] == '*')) - assert.Equal(t, tc.expect, actual) + actual := string(internal.StripCR([]byte(tc.input), + len(tc.input) >= 2 && tc.input[1] == '*')) + require.Equal(t, tc.expect, actual) } } -func scanExpect(t *testing.T, input string, mode scanner.Mode, expected ...scanResult) bool { +func scanExpect( + t *testing.T, + input string, + mode internal.ScanMode, + expected ...scanResult, +) { testFile := testFileSet.AddFile("test", -1, len(input)) - s := scanner.NewScanner( + s := internal.NewScanner( testFile, []byte(input), - func(_ source.FilePos, msg string) { assert.Fail(t, msg) }, + func(_ internal.SourceFilePos, msg string) { require.Fail(t, msg) }, mode) for idx, e := range expected { @@ -226,32 +235,28 @@ func scanExpect(t *testing.T, input string, mode scanner.Mode, expected ...scanR filePos := testFile.Position(pos) - if !assert.Equal(t, e.Token, tok, "[%d] expected: %s, actual: %s", idx, e.Token.String(), tok.String()) || - !assert.Equal(t, e.Literal, literal) || - !assert.Equal(t, e.Line, filePos.Line) || - !assert.Equal(t, e.Column, filePos.Column) { - return false - } + require.Equal(t, e.Token, tok, "[%d] expected: %s, actual: %s", + idx, e.Token.String(), tok.String()) + require.Equal(t, e.Literal, literal) + require.Equal(t, e.Line, filePos.Line) + require.Equal(t, e.Column, filePos.Column) } tok, _, _ := s.Scan() - assert.Equal(t, token.EOF, tok, "more tokens left") - - return assert.Equal(t, 0, s.ErrorCount()) + require.Equal(t, token.EOF, tok, "more tokens left") + require.Equal(t, 0, s.ErrorCount()) } func countLines(s string) int { if s == "" { return 0 } - n := 1 for i := 0; i < len(s); i++ { if s[i] == '\n' { n++ } } - return n } diff --git a/internal/source_file.go b/internal/source_file.go new file mode 100644 index 0000000..6068b13 --- /dev/null +++ b/internal/source_file.go @@ -0,0 +1,231 @@ +package internal + +import ( + "fmt" + "sort" +) + +// SourceFilePos represents a position information in the file. +type SourceFilePos struct { + Filename string // filename, if any + Offset int // offset, starting at 0 + Line int // line number, starting at 1 + Column int // column number, starting at 1 (byte count) +} + +// IsValid returns true if the position is valid. +func (p SourceFilePos) IsValid() bool { + return p.Line > 0 +} + +// String returns a string in one of several forms: +// +// file:line:column valid position with file name +// file:line valid position with file name but no column (column == 0) +// line:column valid position without file name +// line valid position without file name and no column (column == 0) +// file invalid position with file name +// - invalid position without file name +// +func (p SourceFilePos) String() string { + s := p.Filename + if p.IsValid() { + if s != "" { + s += ":" + } + s += fmt.Sprintf("%d", p.Line) + if p.Column != 0 { + s += fmt.Sprintf(":%d", p.Column) + } + } + if s == "" { + s = "-" + } + return s +} + +// SourceFileSet represents a set of source files. +type SourceFileSet struct { + Base int // base offset for the next file + Files []*SourceFile // list of files in the order added to the set + LastFile *SourceFile // cache of last file looked up +} + +// NewFileSet creates a new file set. +func NewFileSet() *SourceFileSet { + return &SourceFileSet{ + Base: 1, // 0 == NoPos + } +} + +// AddFile adds a new file in the file set. +func (s *SourceFileSet) AddFile(filename string, base, size int) *SourceFile { + if base < 0 { + base = s.Base + } + if base < s.Base || size < 0 { + panic("illegal base or size") + } + f := &SourceFile{ + set: s, + Name: filename, + Base: base, + Size: size, + Lines: []int{0}, + } + base += size + 1 // +1 because EOF also has a position + if base < 0 { + panic("offset overflow (> 2G of source code in file set)") + } + + // add the file to the file set + s.Base = base + s.Files = append(s.Files, f) + s.LastFile = f + return f +} + +// File returns the file that contains the position p. If no such file is +// found (for instance for p == NoPos), the result is nil. +func (s *SourceFileSet) File(p Pos) (f *SourceFile) { + if p != NoPos { + f = s.file(p) + } + return +} + +// Position converts a SourcePos p in the fileset into a SourceFilePos value. +func (s *SourceFileSet) Position(p Pos) (pos SourceFilePos) { + if p != NoPos { + if f := s.file(p); f != nil { + return f.position(p) + } + } + return +} + +func (s *SourceFileSet) file(p Pos) *SourceFile { + // common case: p is in last file + f := s.LastFile + if f != nil && f.Base <= int(p) && int(p) <= f.Base+f.Size { + return f + } + + // p is not in last file - search all files + if i := searchFiles(s.Files, int(p)); i >= 0 { + f := s.Files[i] + + // f.base <= int(p) by definition of searchFiles + if int(p) <= f.Base+f.Size { + s.LastFile = f // race is ok - s.last is only a cache + return f + } + } + return nil +} + +func searchFiles(a []*SourceFile, x int) int { + return sort.Search(len(a), func(i int) bool { return a[i].Base > x }) - 1 +} + +// SourceFile represents a source file. +type SourceFile struct { + // SourceFile set for the file + set *SourceFileSet + // SourceFile name as provided to AddFile + Name string + // SourcePos value range for this file is [base...base+size] + Base int + // SourceFile size as provided to AddFile + Size int + // Lines contains the offset of the first character for each line + // (the first entry is always 0) + Lines []int +} + +// Set returns SourceFileSet. +func (f *SourceFile) Set() *SourceFileSet { + return f.set +} + +// LineCount returns the current number of lines. +func (f *SourceFile) LineCount() int { + return len(f.Lines) +} + +// AddLine adds a new line. +func (f *SourceFile) AddLine(offset int) { + i := len(f.Lines) + if (i == 0 || f.Lines[i-1] < offset) && offset < f.Size { + f.Lines = append(f.Lines, offset) + } +} + +// LineStart returns the position of the first character in the line. +func (f *SourceFile) LineStart(line int) Pos { + if line < 1 { + panic("illegal line number (line numbering starts at 1)") + } + if line > len(f.Lines) { + panic("illegal line number") + } + return Pos(f.Base + f.Lines[line-1]) +} + +// FileSetPos returns the position in the file set. +func (f *SourceFile) FileSetPos(offset int) Pos { + if offset > f.Size { + panic("illegal file offset") + } + return Pos(f.Base + offset) +} + +// Offset translates the file set position into the file offset. +func (f *SourceFile) Offset(p Pos) int { + if int(p) < f.Base || int(p) > f.Base+f.Size { + panic("illegal SourcePos value") + } + return int(p) - f.Base +} + +// Position translates the file set position into the file position. +func (f *SourceFile) Position(p Pos) (pos SourceFilePos) { + if p != NoPos { + if int(p) < f.Base || int(p) > f.Base+f.Size { + panic("illegal SourcePos value") + } + pos = f.position(p) + } + return +} + +func (f *SourceFile) position(p Pos) (pos SourceFilePos) { + offset := int(p) - f.Base + pos.Offset = offset + pos.Filename, pos.Line, pos.Column = f.unpack(offset) + return +} + +func (f *SourceFile) unpack(offset int) (filename string, line, column int) { + filename = f.Name + if i := searchInts(f.Lines, offset); i >= 0 { + line, column = i+1, offset-f.Lines[i]+1 + } + return +} + +func searchInts(a []int, x int) int { + // This function body is a manually inlined version of: + // return sort.Search(len(a), func(i int) bool { return a[i] > x }) - 1 + i, j := 0, len(a) + for i < j { + h := i + (j-i)/2 // avoid overflow when computing h + // i ≤ h < j + if a[h] <= x { + i = h + 1 + } else { + j = h + } + } + return i - 1 +} diff --git a/internal/stmt.go b/internal/stmt.go new file mode 100644 index 0000000..70458a2 --- /dev/null +++ b/internal/stmt.go @@ -0,0 +1,349 @@ +package internal + +import ( + "strings" + + "github.com/d5/tengo/internal/token" +) + +// Stmt represents a statement in the AST. +type Stmt interface { + Node + stmtNode() +} + +// AssignStmt represents an assignment statement. +type AssignStmt struct { + LHS []Expr + RHS []Expr + Token token.Token + TokenPos Pos +} + +func (s *AssignStmt) stmtNode() {} + +// Pos returns the position of first character belonging to the node. +func (s *AssignStmt) Pos() Pos { + return s.LHS[0].Pos() +} + +// End returns the position of first character immediately after the node. +func (s *AssignStmt) End() Pos { + return s.RHS[len(s.RHS)-1].End() +} + +func (s *AssignStmt) String() string { + var lhs, rhs []string + for _, e := range s.LHS { + lhs = append(lhs, e.String()) + } + for _, e := range s.RHS { + rhs = append(rhs, e.String()) + } + return strings.Join(lhs, ", ") + " " + s.Token.String() + + " " + strings.Join(rhs, ", ") +} + +// BadStmt represents a bad statement. +type BadStmt struct { + From Pos + To Pos +} + +func (s *BadStmt) stmtNode() {} + +// Pos returns the position of first character belonging to the node. +func (s *BadStmt) Pos() Pos { + return s.From +} + +// End returns the position of first character immediately after the node. +func (s *BadStmt) End() Pos { + return s.To +} + +func (s *BadStmt) String() string { + return "" +} + +// BlockStmt represents a block statement. +type BlockStmt struct { + Stmts []Stmt + LBrace Pos + RBrace Pos +} + +func (s *BlockStmt) stmtNode() {} + +// Pos returns the position of first character belonging to the node. +func (s *BlockStmt) Pos() Pos { + return s.LBrace +} + +// End returns the position of first character immediately after the node. +func (s *BlockStmt) End() Pos { + return s.RBrace + 1 +} + +func (s *BlockStmt) String() string { + var list []string + for _, e := range s.Stmts { + list = append(list, e.String()) + } + return "{" + strings.Join(list, "; ") + "}" +} + +// BranchStmt represents a branch statement. +type BranchStmt struct { + Token token.Token + TokenPos Pos + Label *Ident +} + +func (s *BranchStmt) stmtNode() {} + +// Pos returns the position of first character belonging to the node. +func (s *BranchStmt) Pos() Pos { + return s.TokenPos +} + +// End returns the position of first character immediately after the node. +func (s *BranchStmt) End() Pos { + if s.Label != nil { + return s.Label.End() + } + + return Pos(int(s.TokenPos) + len(s.Token.String())) +} + +func (s *BranchStmt) String() string { + var label string + if s.Label != nil { + label = " " + s.Label.Name + } + return s.Token.String() + label +} + +// EmptyStmt represents an empty statement. +type EmptyStmt struct { + Semicolon Pos + Implicit bool +} + +func (s *EmptyStmt) stmtNode() {} + +// Pos returns the position of first character belonging to the node. +func (s *EmptyStmt) Pos() Pos { + return s.Semicolon +} + +// End returns the position of first character immediately after the node. +func (s *EmptyStmt) End() Pos { + if s.Implicit { + return s.Semicolon + } + return s.Semicolon + 1 +} + +func (s *EmptyStmt) String() string { + return ";" +} + +// ExportStmt represents an export statement. +type ExportStmt struct { + ExportPos Pos + Result Expr +} + +func (s *ExportStmt) stmtNode() {} + +// Pos returns the position of first character belonging to the node. +func (s *ExportStmt) Pos() Pos { + return s.ExportPos +} + +// End returns the position of first character immediately after the node. +func (s *ExportStmt) End() Pos { + return s.Result.End() +} + +func (s *ExportStmt) String() string { + return "export " + s.Result.String() +} + +// ExprStmt represents an expression statement. +type ExprStmt struct { + Expr Expr +} + +func (s *ExprStmt) stmtNode() {} + +// Pos returns the position of first character belonging to the node. +func (s *ExprStmt) Pos() Pos { + return s.Expr.Pos() +} + +// End returns the position of first character immediately after the node. +func (s *ExprStmt) End() Pos { + return s.Expr.End() +} + +func (s *ExprStmt) String() string { + return s.Expr.String() +} + +// ForInStmt represents a for-in statement. +type ForInStmt struct { + ForPos Pos + Key *Ident + Value *Ident + Iterable Expr + Body *BlockStmt +} + +func (s *ForInStmt) stmtNode() {} + +// Pos returns the position of first character belonging to the node. +func (s *ForInStmt) Pos() Pos { + return s.ForPos +} + +// End returns the position of first character immediately after the node. +func (s *ForInStmt) End() Pos { + return s.Body.End() +} + +func (s *ForInStmt) String() string { + if s.Value != nil { + return "for " + s.Key.String() + ", " + s.Value.String() + + " in " + s.Iterable.String() + " " + s.Body.String() + } + return "for " + s.Key.String() + " in " + s.Iterable.String() + + " " + s.Body.String() +} + +// ForStmt represents a for statement. +type ForStmt struct { + ForPos Pos + Init Stmt + Cond Expr + Post Stmt + Body *BlockStmt +} + +func (s *ForStmt) stmtNode() {} + +// Pos returns the position of first character belonging to the node. +func (s *ForStmt) Pos() Pos { + return s.ForPos +} + +// End returns the position of first character immediately after the node. +func (s *ForStmt) End() Pos { + return s.Body.End() +} + +func (s *ForStmt) String() string { + var init, cond, post string + if s.Init != nil { + init = s.Init.String() + } + if s.Cond != nil { + cond = s.Cond.String() + " " + } + if s.Post != nil { + post = s.Post.String() + } + + if init != "" || post != "" { + return "for " + init + " ; " + cond + " ; " + post + s.Body.String() + } + return "for " + cond + s.Body.String() +} + +// IfStmt represents an if statement. +type IfStmt struct { + IfPos Pos + Init Stmt + Cond Expr + Body *BlockStmt + Else Stmt // else branch; or nil +} + +func (s *IfStmt) stmtNode() {} + +// Pos returns the position of first character belonging to the node. +func (s *IfStmt) Pos() Pos { + return s.IfPos +} + +// End returns the position of first character immediately after the node. +func (s *IfStmt) End() Pos { + if s.Else != nil { + return s.Else.End() + } + return s.Body.End() +} + +func (s *IfStmt) String() string { + var initStmt, elseStmt string + if s.Init != nil { + initStmt = s.Init.String() + "; " + } + if s.Else != nil { + elseStmt = " else " + s.Else.String() + } + return "if " + initStmt + s.Cond.String() + " " + + s.Body.String() + elseStmt +} + +// IncDecStmt represents increment or decrement statement. +type IncDecStmt struct { + Expr Expr + Token token.Token + TokenPos Pos +} + +func (s *IncDecStmt) stmtNode() {} + +// Pos returns the position of first character belonging to the node. +func (s *IncDecStmt) Pos() Pos { + return s.Expr.Pos() +} + +// End returns the position of first character immediately after the node. +func (s *IncDecStmt) End() Pos { + return Pos(int(s.TokenPos) + 2) +} + +func (s *IncDecStmt) String() string { + return s.Expr.String() + s.Token.String() +} + +// ReturnStmt represents a return statement. +type ReturnStmt struct { + ReturnPos Pos + Result Expr +} + +func (s *ReturnStmt) stmtNode() {} + +// Pos returns the position of first character belonging to the node. +func (s *ReturnStmt) Pos() Pos { + return s.ReturnPos +} + +// End returns the position of first character immediately after the node. +func (s *ReturnStmt) End() Pos { + if s.Result != nil { + return s.Result.End() + } + return s.ReturnPos + 6 +} + +func (s *ReturnStmt) String() string { + if s.Result != nil { + return "return " + s.Result.String() + } + return "return" +} diff --git a/internal/symbol_table_test.go b/internal/symbol_table_test.go new file mode 100644 index 0000000..c46e718 --- /dev/null +++ b/internal/symbol_table_test.go @@ -0,0 +1,133 @@ +package internal_test + +import ( + "testing" + + "github.com/d5/tengo/internal" + "github.com/d5/tengo/internal/require" +) + +func TestSymbolTable(t *testing.T) { + /* + GLOBAL + [0] a + [1] b + + LOCAL 1 + [0] d + + LOCAL 2 + [0] e + [1] f + + LOCAL 2 BLOCK 1 + [2] g + [3] h + + LOCAL 2 BLOCK 2 + [2] i + [3] j + [4] k + + LOCAL 1 BLOCK 1 + [1] l + [2] m + [3] n + [4] o + [5] p + + LOCAL 3 + [0] q + [1] r + */ + + global := symbolTable() + require.Equal(t, globalSymbol("a", 0), global.Define("a")) + require.Equal(t, globalSymbol("b", 1), global.Define("b")) + + local1 := global.Fork(false) + require.Equal(t, localSymbol("d", 0), local1.Define("d")) + + local1Block1 := local1.Fork(true) + require.Equal(t, localSymbol("l", 1), local1Block1.Define("l")) + require.Equal(t, localSymbol("m", 2), local1Block1.Define("m")) + require.Equal(t, localSymbol("n", 3), local1Block1.Define("n")) + require.Equal(t, localSymbol("o", 4), local1Block1.Define("o")) + require.Equal(t, localSymbol("p", 5), local1Block1.Define("p")) + + local2 := local1.Fork(false) + require.Equal(t, localSymbol("e", 0), local2.Define("e")) + require.Equal(t, localSymbol("f", 1), local2.Define("f")) + + local2Block1 := local2.Fork(true) + require.Equal(t, localSymbol("g", 2), local2Block1.Define("g")) + require.Equal(t, localSymbol("h", 3), local2Block1.Define("h")) + + local2Block2 := local2.Fork(true) + require.Equal(t, localSymbol("i", 2), local2Block2.Define("i")) + require.Equal(t, localSymbol("j", 3), local2Block2.Define("j")) + require.Equal(t, localSymbol("k", 4), local2Block2.Define("k")) + + local3 := local1Block1.Fork(false) + require.Equal(t, localSymbol("q", 0), local3.Define("q")) + require.Equal(t, localSymbol("r", 1), local3.Define("r")) + + require.Equal(t, 2, global.MaxSymbols()) + require.Equal(t, 6, local1.MaxSymbols()) + require.Equal(t, 6, local1Block1.MaxSymbols()) + require.Equal(t, 5, local2.MaxSymbols()) + require.Equal(t, 4, local2Block1.MaxSymbols()) + require.Equal(t, 5, local2Block2.MaxSymbols()) + require.Equal(t, 2, local3.MaxSymbols()) + + resolveExpect(t, global, "a", globalSymbol("a", 0), 0) + resolveExpect(t, local1, "d", localSymbol("d", 0), 0) + resolveExpect(t, local1, "a", globalSymbol("a", 0), 1) + resolveExpect(t, local3, "a", globalSymbol("a", 0), 3) + resolveExpect(t, local3, "d", freeSymbol("d", 0), 2) + resolveExpect(t, local3, "r", localSymbol("r", 1), 0) + resolveExpect(t, local2Block2, "k", localSymbol("k", 4), 0) + resolveExpect(t, local2Block2, "e", localSymbol("e", 0), 1) + resolveExpect(t, local2Block2, "b", globalSymbol("b", 1), 3) +} + +func symbol( + name string, + scope internal.SymbolScope, + index int, +) *internal.Symbol { + return &internal.Symbol{ + Name: name, + Scope: scope, + Index: index, + } +} + +func globalSymbol(name string, index int) *internal.Symbol { + return symbol(name, internal.ScopeGlobal, index) +} + +func localSymbol(name string, index int) *internal.Symbol { + return symbol(name, internal.ScopeLocal, index) +} + +func freeSymbol(name string, index int) *internal.Symbol { + return symbol(name, internal.ScopeFree, index) +} + +func symbolTable() *internal.SymbolTable { + return internal.NewSymbolTable() +} + +func resolveExpect( + t *testing.T, + symbolTable *internal.SymbolTable, + name string, + expectedSymbol *internal.Symbol, + expectedDepth int, +) { + actualSymbol, actualDepth, ok := symbolTable.Resolve(name) + require.True(t, ok) + require.Equal(t, expectedSymbol, actualSymbol) + require.Equal(t, expectedDepth, actualDepth) +} diff --git a/compiler/token/tokens.go b/internal/token/tokens.go similarity index 91% rename from compiler/token/tokens.go rename to internal/token/tokens.go index b32d36e..4e6aa80 100644 --- a/compiler/token/tokens.go +++ b/internal/token/tokens.go @@ -2,6 +2,8 @@ package token import "strconv" +var keywords map[string]Token + // Token represents a token. type Token int @@ -206,3 +208,18 @@ func (tok Token) IsOperator() bool { func (tok Token) IsKeyword() bool { return _keywordBeg < tok && tok < _keywordEnd } + +// Lookup returns corresponding keyword if ident is a keyword. +func Lookup(ident string) Token { + if tok, isKeyword := keywords[ident]; isKeyword { + return tok + } + return Ident +} + +func init() { + keywords = make(map[string]Token) + for i := _keywordBeg + 1; i < _keywordEnd; i++ { + keywords[tokens[i]] = i + } +} diff --git a/iterator.go b/iterator.go new file mode 100644 index 0000000..13adbba --- /dev/null +++ b/iterator.go @@ -0,0 +1,209 @@ +package tengo + +// Iterator represents an iterator for underlying data type. +type Iterator interface { + Object + + // Next returns true if there are more elements to iterate. + Next() bool + + // Key returns the key or index value of the current element. + Key() Object + + // Value returns the value of the current element. + Value() Object +} + +// ArrayIterator is an iterator for an array. +type ArrayIterator struct { + ObjectImpl + v []Object + i int + l int +} + +// TypeName returns the name of the type. +func (i *ArrayIterator) TypeName() string { + return "array-iterator" +} + +func (i *ArrayIterator) String() string { + return "" +} + +// IsFalsy returns true if the value of the type is falsy. +func (i *ArrayIterator) IsFalsy() bool { + return true +} + +// Equals returns true if the value of the type is equal to the value of +// another object. +func (i *ArrayIterator) Equals(Object) bool { + return false +} + +// Copy returns a copy of the type. +func (i *ArrayIterator) Copy() Object { + return &ArrayIterator{v: i.v, i: i.i, l: i.l} +} + +// Next returns true if there are more elements to iterate. +func (i *ArrayIterator) Next() bool { + i.i++ + return i.i <= i.l +} + +// Key returns the key or index value of the current element. +func (i *ArrayIterator) Key() Object { + return &Int{Value: int64(i.i - 1)} +} + +// Value returns the value of the current element. +func (i *ArrayIterator) Value() Object { + return i.v[i.i-1] +} + +// BytesIterator represents an iterator for a string. +type BytesIterator struct { + ObjectImpl + v []byte + i int + l int +} + +// TypeName returns the name of the type. +func (i *BytesIterator) TypeName() string { + return "bytes-iterator" +} + +func (i *BytesIterator) String() string { + return "" +} + +// Equals returns true if the value of the type is equal to the value of +// another object. +func (i *BytesIterator) Equals(Object) bool { + return false +} + +// Copy returns a copy of the type. +func (i *BytesIterator) Copy() Object { + return &BytesIterator{v: i.v, i: i.i, l: i.l} +} + +// Next returns true if there are more elements to iterate. +func (i *BytesIterator) Next() bool { + i.i++ + return i.i <= i.l +} + +// Key returns the key or index value of the current element. +func (i *BytesIterator) Key() Object { + return &Int{Value: int64(i.i - 1)} +} + +// Value returns the value of the current element. +func (i *BytesIterator) Value() Object { + return &Int{Value: int64(i.v[i.i-1])} +} + +// MapIterator represents an iterator for the map. +type MapIterator struct { + ObjectImpl + v map[string]Object + k []string + i int + l int +} + +// TypeName returns the name of the type. +func (i *MapIterator) TypeName() string { + return "map-iterator" +} + +func (i *MapIterator) String() string { + return "" +} + +// IsFalsy returns true if the value of the type is falsy. +func (i *MapIterator) IsFalsy() bool { + return true +} + +// Equals returns true if the value of the type is equal to the value of +// another object. +func (i *MapIterator) Equals(Object) bool { + return false +} + +// Copy returns a copy of the type. +func (i *MapIterator) Copy() Object { + return &MapIterator{v: i.v, k: i.k, i: i.i, l: i.l} +} + +// Next returns true if there are more elements to iterate. +func (i *MapIterator) Next() bool { + i.i++ + return i.i <= i.l +} + +// Key returns the key or index value of the current element. +func (i *MapIterator) Key() Object { + k := i.k[i.i-1] + return &String{Value: k} +} + +// Value returns the value of the current element. +func (i *MapIterator) Value() Object { + k := i.k[i.i-1] + return i.v[k] +} + +// StringIterator represents an iterator for a string. +type StringIterator struct { + ObjectImpl + v []rune + i int + l int +} + +// TypeName returns the name of the type. +func (i *StringIterator) TypeName() string { + return "string-iterator" +} + +func (i *StringIterator) String() string { + return "" +} + +// IsFalsy returns true if the value of the type is falsy. +func (i *StringIterator) IsFalsy() bool { + return true +} + +// Equals returns true if the value of the type is equal to the value of +// another object. +func (i *StringIterator) Equals(Object) bool { + return false +} + +// Copy returns a copy of the type. +func (i *StringIterator) Copy() Object { + return &StringIterator{v: i.v, i: i.i, l: i.l} +} + +// Next returns true if there are more elements to iterate. +func (i *StringIterator) Next() bool { + i.i++ + return i.i <= i.l +} + +// Key returns the key or index value of the current element. +func (i *StringIterator) Key() Object { + return &Int{Value: int64(i.i - 1)} +} + +// Value returns the value of the current element. +func (i *StringIterator) Value() Object { + return &Char{Value: i.v[i.i-1]} +} diff --git a/objects/module_map.go b/modules.go similarity index 63% rename from objects/module_map.go rename to modules.go index 874b8a2..c8fcde7 100644 --- a/objects/module_map.go +++ b/modules.go @@ -1,7 +1,13 @@ -package objects +package tengo -// ModuleMap represents a set of named modules. -// Use NewModuleMap to create a new module map. +// Importable interface represents importable module instance. +type Importable interface { + // Import should return either an Object or module source code ([]byte). + Import(moduleName string) (interface{}, error) +} + +// ModuleMap represents a set of named modules. Use NewModuleMap to create a +// new module map. type ModuleMap struct { m map[string]Importable } @@ -33,21 +39,21 @@ func (m *ModuleMap) Remove(name string) { delete(m.m, name) } -// Get returns an import module identified by name. -// It returns if the name is not found. +// Get returns an import module identified by name. It returns if the name is +// not found. func (m *ModuleMap) Get(name string) Importable { return m.m[name] } -// GetBuiltinModule returns a builtin module identified by name. -// It returns if the name is not found or the module is not a builtin module. +// GetBuiltinModule returns a builtin module identified by name. It returns +// if the name is not found or the module is not a builtin module. func (m *ModuleMap) GetBuiltinModule(name string) *BuiltinModule { mod, _ := m.m[name].(*BuiltinModule) return mod } -// GetSourceModule returns a source module identified by name. -// It returns if the name is not found or the module is not a source module. +// GetSourceModule returns a source module identified by name. It returns if +// the name is not found or the module is not a source module. func (m *ModuleMap) GetSourceModule(name string) *SourceModule { mod, _ := m.m[name].(*SourceModule) return mod @@ -75,3 +81,13 @@ func (m *ModuleMap) AddMap(o *ModuleMap) { m.m[name] = mod } } + +// SourceModule is an importable module that's written in Tengo. +type SourceModule struct { + Src []byte +} + +// Import returns a module source code. +func (m *SourceModule) Import(_ string) (interface{}, error) { + return m.Src, nil +} diff --git a/objects.go b/objects.go new file mode 100644 index 0000000..2b49439 --- /dev/null +++ b/objects.go @@ -0,0 +1,1581 @@ +package tengo + +import ( + "bytes" + "fmt" + "math" + "strconv" + "strings" + "time" + + "github.com/d5/tengo/internal" + "github.com/d5/tengo/internal/token" +) + +var ( + // TrueValue represents a true value. + TrueValue Object = &Bool{value: true} + + // FalseValue represents a false value. + FalseValue Object = &Bool{value: false} + + // UndefinedValue represents an undefined value. + UndefinedValue Object = &Undefined{} +) + +// Object represents an object in the VM. +type Object interface { + // TypeName should return the name of the type. + TypeName() string + + // String should return a string representation of the type's value. + String() string + + // BinaryOp should return another object that is the result of a given + // binary operator and a right-hand side object. If BinaryOp returns an + // error, the VM will treat it as a run-time error. + BinaryOp(op token.Token, rhs Object) (Object, error) + + // IsFalsy should return true if the value of the type should be considered + // as falsy. + IsFalsy() bool + + // Equals should return true if the value of the type should be considered + // as equal to the value of another object. + Equals(another Object) bool + + // Copy should return a copy of the type (and its value). Copy function + // will be used for copy() builtin function which is expected to deep-copy + // the values generally. + Copy() Object + + // IndexGet should take an index Object and return a result Object or an + // error for indexable objects. Indexable is an object that can take an + // index and return an object. If error is returned, the runtime will treat + // it as a run-time error and ignore returned value. If Object is not + // indexable, ErrNotIndexable should be returned as error. If nil is + // returned as value, it will be converted to UndefinedToken value by the + // runtime. + IndexGet(index Object) (value Object, err error) + + // IndexSet should take an index Object and a value Object for index + // assignable objects. Index assignable is an object that can take an index + // and a value on the left-hand side of the assignment statement. If Object + // is not index assignable, ErrNotIndexAssignable should be returned as + // error. If an error is returned, it will be treated as a run-time error. + IndexSet(index, value Object) error + + // Iterate should return an Iterator for the type. + Iterate() Iterator + + // CanIterate should return whether the Object can be Iterated. + CanIterate() bool + + // Call should take an arbitrary number of arguments and returns a return + // value and/or an error, which the VM will consider as a run-time error. + Call(args ...Object) (ret Object, err error) + + // CanCall should return whether the Object can be Called. + CanCall() bool +} + +// ObjectImpl represents a default Object Implementation. To defined a new +// value type, one can embed ObjectImpl in their type declarations to avoid +// implementing all non-significant methods. TypeName() and String() methods +// still need to be implemented. +type ObjectImpl struct { +} + +// TypeName returns the name of the type. +func (o *ObjectImpl) TypeName() string { + panic(ErrNotImplemented) +} + +func (o *ObjectImpl) String() string { + panic(ErrNotImplemented) +} + +// BinaryOp returns another object that is the result of a given binary +// operator and a right-hand side object. +func (o *ObjectImpl) BinaryOp(_ token.Token, _ Object) (Object, error) { + return nil, ErrInvalidOperator +} + +// Copy returns a copy of the type. +func (o *ObjectImpl) Copy() Object { + return nil +} + +// IsFalsy returns true if the value of the type is falsy. +func (o *ObjectImpl) IsFalsy() bool { + return false +} + +// Equals returns true if the value of the type is equal to the value of +// another object. +func (o *ObjectImpl) Equals(x Object) bool { + return o == x +} + +// IndexGet returns an element at a given index. +func (o *ObjectImpl) IndexGet(_ Object) (res Object, err error) { + return nil, ErrNotIndexable +} + +// IndexSet sets an element at a given index. +func (o *ObjectImpl) IndexSet(_, _ Object) (err error) { + return ErrNotIndexAssignable +} + +// Iterate returns an iterator. +func (o *ObjectImpl) Iterate() Iterator { + return nil +} + +// CanIterate returns whether the Object can be Iterated. +func (o *ObjectImpl) CanIterate() bool { + return false +} + +// Call takes an arbitrary number of arguments and returns a return value +// and/or an error. +func (o *ObjectImpl) Call(_ ...Object) (ret Object, err error) { + return nil, nil +} + +// CanCall returns whether the Object can be Called. +func (o *ObjectImpl) CanCall() bool { + return false +} + +// Array represents an array of objects. +type Array struct { + ObjectImpl + Value []Object +} + +// TypeName returns the name of the type. +func (o *Array) TypeName() string { + return "array" +} + +func (o *Array) String() string { + var elements []string + for _, e := range o.Value { + elements = append(elements, e.String()) + } + return fmt.Sprintf("[%s]", strings.Join(elements, ", ")) +} + +// BinaryOp returns another object that is the result of a given binary +// operator and a right-hand side object. +func (o *Array) BinaryOp(op token.Token, rhs Object) (Object, error) { + if rhs, ok := rhs.(*Array); ok { + switch op { + case token.Add: + if len(rhs.Value) == 0 { + return o, nil + } + return &Array{Value: append(o.Value, rhs.Value...)}, nil + } + } + return nil, ErrInvalidOperator +} + +// Copy returns a copy of the type. +func (o *Array) Copy() Object { + var c []Object + for _, elem := range o.Value { + c = append(c, elem.Copy()) + } + return &Array{Value: c} +} + +// IsFalsy returns true if the value of the type is falsy. +func (o *Array) IsFalsy() bool { + return len(o.Value) == 0 +} + +// Equals returns true if the value of the type is equal to the value of +// another object. +func (o *Array) Equals(x Object) bool { + var xVal []Object + switch x := x.(type) { + case *Array: + xVal = x.Value + case *ImmutableArray: + xVal = x.Value + default: + return false + } + if len(o.Value) != len(xVal) { + return false + } + for i, e := range o.Value { + if !e.Equals(xVal[i]) { + return false + } + } + return true +} + +// IndexGet returns an element at a given index. +func (o *Array) IndexGet(index Object) (res Object, err error) { + intIdx, ok := index.(*Int) + if !ok { + err = ErrInvalidIndexType + return + } + idxVal := int(intIdx.Value) + if idxVal < 0 || idxVal >= len(o.Value) { + res = UndefinedValue + return + } + res = o.Value[idxVal] + return +} + +// IndexSet sets an element at a given index. +func (o *Array) IndexSet(index, value Object) (err error) { + intIdx, ok := ToInt(index) + if !ok { + err = ErrInvalidIndexType + return + } + if intIdx < 0 || intIdx >= len(o.Value) { + err = ErrIndexOutOfBounds + return + } + o.Value[intIdx] = value + return nil +} + +// Iterate creates an array iterator. +func (o *Array) Iterate() Iterator { + return &ArrayIterator{ + v: o.Value, + l: len(o.Value), + } +} + +// CanIterate returns whether the Object can be Iterated. +func (o *Array) CanIterate() bool { + return true +} + +// Bool represents a boolean value. +type Bool struct { + ObjectImpl + + // this is intentionally non-public to force using objects.TrueValue and + // FalseValue always + value bool +} + +func (o *Bool) String() string { + if o.value { + return "true" + } + + return "false" +} + +// TypeName returns the name of the type. +func (o *Bool) TypeName() string { + return "bool" +} + +// Copy returns a copy of the type. +func (o *Bool) Copy() Object { + return o +} + +// IsFalsy returns true if the value of the type is falsy. +func (o *Bool) IsFalsy() bool { + return !o.value +} + +// Equals returns true if the value of the type is equal to the value of +// another object. +func (o *Bool) Equals(x Object) bool { + return o == x +} + +// GobDecode decodes bool value from input bytes. +func (o *Bool) GobDecode(b []byte) (err error) { + o.value = b[0] == 1 + return +} + +// GobEncode encodes bool values into bytes. +func (o *Bool) GobEncode() (b []byte, err error) { + if o.value { + b = []byte{1} + } else { + b = []byte{0} + } + return +} + +// BuiltinFunction represents a builtin function. +type BuiltinFunction struct { + ObjectImpl + Name string + Value CallableFunc +} + +// TypeName returns the name of the type. +func (o *BuiltinFunction) TypeName() string { + return "builtin-function:" + o.Name +} + +func (o *BuiltinFunction) String() string { + return "" +} + +// Copy returns a copy of the type. +func (o *BuiltinFunction) Copy() Object { + return &BuiltinFunction{Value: o.Value} +} + +// Equals returns true if the value of the type is equal to the value of +// another object. +func (o *BuiltinFunction) Equals(_ Object) bool { + return false +} + +// Call executes a builtin function. +func (o *BuiltinFunction) Call(args ...Object) (Object, error) { + return o.Value(args...) +} + +// CanCall returns whether the Object can be Called. +func (o *BuiltinFunction) CanCall() bool { + return true +} + +// BuiltinModule is an importable module that's written in Go. +type BuiltinModule struct { + Attrs map[string]Object +} + +// Import returns an immutable map for the module. +func (m *BuiltinModule) Import(moduleName string) (interface{}, error) { + return m.AsImmutableMap(moduleName), nil +} + +// AsImmutableMap converts builtin module into an immutable map. +func (m *BuiltinModule) AsImmutableMap(moduleName string) *ImmutableMap { + attrs := make(map[string]Object, len(m.Attrs)) + for k, v := range m.Attrs { + attrs[k] = v.Copy() + } + attrs["__module_name__"] = &String{Value: moduleName} + return &ImmutableMap{Value: attrs} +} + +// Bytes represents a byte array. +type Bytes struct { + ObjectImpl + Value []byte +} + +func (o *Bytes) String() string { + return string(o.Value) +} + +// TypeName returns the name of the type. +func (o *Bytes) TypeName() string { + return "bytes" +} + +// BinaryOp returns another object that is the result of a given binary +// operator and a right-hand side object. +func (o *Bytes) BinaryOp(op token.Token, rhs Object) (Object, error) { + switch op { + case token.Add: + switch rhs := rhs.(type) { + case *Bytes: + if len(o.Value)+len(rhs.Value) > MaxBytesLen { + return nil, ErrBytesLimit + } + return &Bytes{Value: append(o.Value, rhs.Value...)}, nil + } + } + return nil, ErrInvalidOperator +} + +// Copy returns a copy of the type. +func (o *Bytes) Copy() Object { + return &Bytes{Value: append([]byte{}, o.Value...)} +} + +// IsFalsy returns true if the value of the type is falsy. +func (o *Bytes) IsFalsy() bool { + return len(o.Value) == 0 +} + +// Equals returns true if the value of the type is equal to the value of +// another object. +func (o *Bytes) Equals(x Object) bool { + t, ok := x.(*Bytes) + if !ok { + return false + } + return bytes.Equal(o.Value, t.Value) +} + +// IndexGet returns an element (as Int) at a given index. +func (o *Bytes) IndexGet(index Object) (res Object, err error) { + intIdx, ok := index.(*Int) + if !ok { + err = ErrInvalidIndexType + return + } + idxVal := int(intIdx.Value) + if idxVal < 0 || idxVal >= len(o.Value) { + res = UndefinedValue + return + } + res = &Int{Value: int64(o.Value[idxVal])} + return +} + +// Iterate creates a bytes iterator. +func (o *Bytes) Iterate() Iterator { + return &BytesIterator{ + v: o.Value, + l: len(o.Value), + } +} + +// CanIterate returns whether the Object can be Iterated. +func (o *Bytes) CanIterate() bool { + return true +} + +// Char represents a character value. +type Char struct { + ObjectImpl + Value rune +} + +func (o *Char) String() string { + return string(o.Value) +} + +// TypeName returns the name of the type. +func (o *Char) TypeName() string { + return "char" +} + +// BinaryOp returns another object that is the result of a given binary +// operator and a right-hand side object. +func (o *Char) BinaryOp(op token.Token, rhs Object) (Object, error) { + switch rhs := rhs.(type) { + case *Char: + switch op { + case token.Add: + r := o.Value + rhs.Value + if r == o.Value { + return o, nil + } + return &Char{Value: r}, nil + case token.Sub: + r := o.Value - rhs.Value + if r == o.Value { + return o, nil + } + return &Char{Value: r}, nil + case token.Less: + if o.Value < rhs.Value { + return TrueValue, nil + } + return FalseValue, nil + case token.Greater: + if o.Value > rhs.Value { + return TrueValue, nil + } + return FalseValue, nil + case token.LessEq: + if o.Value <= rhs.Value { + return TrueValue, nil + } + return FalseValue, nil + case token.GreaterEq: + if o.Value >= rhs.Value { + return TrueValue, nil + } + return FalseValue, nil + } + case *Int: + switch op { + case token.Add: + r := o.Value + rune(rhs.Value) + if r == o.Value { + return o, nil + } + return &Char{Value: r}, nil + case token.Sub: + r := o.Value - rune(rhs.Value) + if r == o.Value { + return o, nil + } + return &Char{Value: r}, nil + case token.Less: + if int64(o.Value) < rhs.Value { + return TrueValue, nil + } + return FalseValue, nil + case token.Greater: + if int64(o.Value) > rhs.Value { + return TrueValue, nil + } + return FalseValue, nil + case token.LessEq: + if int64(o.Value) <= rhs.Value { + return TrueValue, nil + } + return FalseValue, nil + case token.GreaterEq: + if int64(o.Value) >= rhs.Value { + return TrueValue, nil + } + return FalseValue, nil + } + } + return nil, ErrInvalidOperator +} + +// Copy returns a copy of the type. +func (o *Char) Copy() Object { + return &Char{Value: o.Value} +} + +// IsFalsy returns true if the value of the type is falsy. +func (o *Char) IsFalsy() bool { + return o.Value == 0 +} + +// Equals returns true if the value of the type is equal to the value of +// another object. +func (o *Char) Equals(x Object) bool { + t, ok := x.(*Char) + if !ok { + return false + } + return o.Value == t.Value +} + +// CompiledFunction represents a compiled function. +type CompiledFunction struct { + ObjectImpl + Instructions []byte + NumLocals int // number of local variables (including function parameters) + NumParameters int + VarArgs bool + SourceMap map[int]internal.Pos + Free []*ObjectPtr +} + +// TypeName returns the name of the type. +func (o *CompiledFunction) TypeName() string { + return "compiled-function" +} + +func (o *CompiledFunction) String() string { + return "" +} + +// Copy returns a copy of the type. +func (o *CompiledFunction) Copy() Object { + return &CompiledFunction{ + Instructions: append([]byte{}, o.Instructions...), + NumLocals: o.NumLocals, + NumParameters: o.NumParameters, + VarArgs: o.VarArgs, + Free: append([]*ObjectPtr{}, o.Free...), // DO NOT Copy() of elements; these are variable pointers + } +} + +// Equals returns true if the value of the type is equal to the value of +// another object. +func (o *CompiledFunction) Equals(_ Object) bool { + return false +} + +// SourcePos returns the source position of the instruction at ip. +func (o *CompiledFunction) SourcePos(ip int) internal.Pos { + for ip >= 0 { + if p, ok := o.SourceMap[ip]; ok { + return p + } + ip-- + } + return internal.NoPos +} + +// CanCall returns whether the Object can be Called. +func (o *CompiledFunction) CanCall() bool { + return true +} + +// Error represents an error value. +type Error struct { + ObjectImpl + Value Object +} + +// TypeName returns the name of the type. +func (o *Error) TypeName() string { + return "error" +} + +func (o *Error) String() string { + if o.Value != nil { + return fmt.Sprintf("error: %s", o.Value.String()) + } + return "error" +} + +// IsFalsy returns true if the value of the type is falsy. +func (o *Error) IsFalsy() bool { + return true // error is always false. +} + +// Copy returns a copy of the type. +func (o *Error) Copy() Object { + return &Error{Value: o.Value.Copy()} +} + +// Equals returns true if the value of the type is equal to the value of +// another object. +func (o *Error) Equals(x Object) bool { + return o == x // pointer equality +} + +// IndexGet returns an element at a given index. +func (o *Error) IndexGet(index Object) (res Object, err error) { + if strIdx, _ := ToString(index); strIdx != "value" { + err = ErrInvalidIndexOnError + return + } + res = o.Value + return +} + +// Float represents a floating point number value. +type Float struct { + ObjectImpl + Value float64 +} + +func (o *Float) String() string { + return strconv.FormatFloat(o.Value, 'f', -1, 64) +} + +// TypeName returns the name of the type. +func (o *Float) TypeName() string { + return "float" +} + +// BinaryOp returns another object that is the result of a given binary +// operator and a right-hand side object. +func (o *Float) BinaryOp(op token.Token, rhs Object) (Object, error) { + switch rhs := rhs.(type) { + case *Float: + switch op { + case token.Add: + r := o.Value + rhs.Value + if r == o.Value { + return o, nil + } + return &Float{Value: r}, nil + case token.Sub: + r := o.Value - rhs.Value + if r == o.Value { + return o, nil + } + return &Float{Value: r}, nil + case token.Mul: + r := o.Value * rhs.Value + if r == o.Value { + return o, nil + } + return &Float{Value: r}, nil + case token.Quo: + r := o.Value / rhs.Value + if r == o.Value { + return o, nil + } + return &Float{Value: r}, nil + case token.Less: + if o.Value < rhs.Value { + return TrueValue, nil + } + return FalseValue, nil + case token.Greater: + if o.Value > rhs.Value { + return TrueValue, nil + } + return FalseValue, nil + case token.LessEq: + if o.Value <= rhs.Value { + return TrueValue, nil + } + return FalseValue, nil + case token.GreaterEq: + if o.Value >= rhs.Value { + return TrueValue, nil + } + return FalseValue, nil + } + case *Int: + switch op { + case token.Add: + r := o.Value + float64(rhs.Value) + if r == o.Value { + return o, nil + } + return &Float{Value: r}, nil + case token.Sub: + r := o.Value - float64(rhs.Value) + if r == o.Value { + return o, nil + } + return &Float{Value: r}, nil + case token.Mul: + r := o.Value * float64(rhs.Value) + if r == o.Value { + return o, nil + } + return &Float{Value: r}, nil + case token.Quo: + r := o.Value / float64(rhs.Value) + if r == o.Value { + return o, nil + } + return &Float{Value: r}, nil + case token.Less: + if o.Value < float64(rhs.Value) { + return TrueValue, nil + } + return FalseValue, nil + case token.Greater: + if o.Value > float64(rhs.Value) { + return TrueValue, nil + } + return FalseValue, nil + case token.LessEq: + if o.Value <= float64(rhs.Value) { + return TrueValue, nil + } + return FalseValue, nil + case token.GreaterEq: + if o.Value >= float64(rhs.Value) { + return TrueValue, nil + } + return FalseValue, nil + } + } + return nil, ErrInvalidOperator +} + +// Copy returns a copy of the type. +func (o *Float) Copy() Object { + return &Float{Value: o.Value} +} + +// IsFalsy returns true if the value of the type is falsy. +func (o *Float) IsFalsy() bool { + return math.IsNaN(o.Value) +} + +// Equals returns true if the value of the type is equal to the value of +// another object. +func (o *Float) Equals(x Object) bool { + t, ok := x.(*Float) + if !ok { + return false + } + return o.Value == t.Value +} + +// ImmutableArray represents an immutable array of objects. +type ImmutableArray struct { + ObjectImpl + Value []Object +} + +// TypeName returns the name of the type. +func (o *ImmutableArray) TypeName() string { + return "immutable-array" +} + +func (o *ImmutableArray) String() string { + var elements []string + for _, e := range o.Value { + elements = append(elements, e.String()) + } + return fmt.Sprintf("[%s]", strings.Join(elements, ", ")) +} + +// BinaryOp returns another object that is the result of a given binary +// operator and a right-hand side object. +func (o *ImmutableArray) BinaryOp(op token.Token, rhs Object) (Object, error) { + if rhs, ok := rhs.(*ImmutableArray); ok { + switch op { + case token.Add: + return &Array{Value: append(o.Value, rhs.Value...)}, nil + } + } + return nil, ErrInvalidOperator +} + +// Copy returns a copy of the type. +func (o *ImmutableArray) Copy() Object { + var c []Object + for _, elem := range o.Value { + c = append(c, elem.Copy()) + } + return &Array{Value: c} +} + +// IsFalsy returns true if the value of the type is falsy. +func (o *ImmutableArray) IsFalsy() bool { + return len(o.Value) == 0 +} + +// Equals returns true if the value of the type is equal to the value of +// another object. +func (o *ImmutableArray) Equals(x Object) bool { + var xVal []Object + switch x := x.(type) { + case *Array: + xVal = x.Value + case *ImmutableArray: + xVal = x.Value + default: + return false + } + if len(o.Value) != len(xVal) { + return false + } + for i, e := range o.Value { + if !e.Equals(xVal[i]) { + return false + } + } + return true +} + +// IndexGet returns an element at a given index. +func (o *ImmutableArray) IndexGet(index Object) (res Object, err error) { + intIdx, ok := index.(*Int) + if !ok { + err = ErrInvalidIndexType + return + } + idxVal := int(intIdx.Value) + if idxVal < 0 || idxVal >= len(o.Value) { + res = UndefinedValue + return + } + res = o.Value[idxVal] + return +} + +// Iterate creates an array iterator. +func (o *ImmutableArray) Iterate() Iterator { + return &ArrayIterator{ + v: o.Value, + l: len(o.Value), + } +} + +// CanIterate returns whether the Object can be Iterated. +func (o *ImmutableArray) CanIterate() bool { + return true +} + +// ImmutableMap represents an immutable map object. +type ImmutableMap struct { + ObjectImpl + Value map[string]Object +} + +// TypeName returns the name of the type. +func (o *ImmutableMap) TypeName() string { + return "immutable-map" +} + +func (o *ImmutableMap) String() string { + var pairs []string + for k, v := range o.Value { + pairs = append(pairs, fmt.Sprintf("%s: %s", k, v.String())) + } + return fmt.Sprintf("{%s}", strings.Join(pairs, ", ")) +} + +// Copy returns a copy of the type. +func (o *ImmutableMap) Copy() Object { + c := make(map[string]Object) + for k, v := range o.Value { + c[k] = v.Copy() + } + return &Map{Value: c} +} + +// IsFalsy returns true if the value of the type is falsy. +func (o *ImmutableMap) IsFalsy() bool { + return len(o.Value) == 0 +} + +// IndexGet returns the value for the given key. +func (o *ImmutableMap) IndexGet(index Object) (res Object, err error) { + strIdx, ok := ToString(index) + if !ok { + err = ErrInvalidIndexType + return + } + res, ok = o.Value[strIdx] + if !ok { + res = UndefinedValue + } + return +} + +// Equals returns true if the value of the type is equal to the value of +// another object. +func (o *ImmutableMap) Equals(x Object) bool { + var xVal map[string]Object + switch x := x.(type) { + case *Map: + xVal = x.Value + case *ImmutableMap: + xVal = x.Value + default: + return false + } + if len(o.Value) != len(xVal) { + return false + } + for k, v := range o.Value { + tv := xVal[k] + if !v.Equals(tv) { + return false + } + } + return true +} + +// Iterate creates an immutable map iterator. +func (o *ImmutableMap) Iterate() Iterator { + var keys []string + for k := range o.Value { + keys = append(keys, k) + } + return &MapIterator{ + v: o.Value, + k: keys, + l: len(keys), + } +} + +// CanIterate returns whether the Object can be Iterated. +func (o *ImmutableMap) CanIterate() bool { + return true +} + +// Int represents an integer value. +type Int struct { + ObjectImpl + Value int64 +} + +func (o *Int) String() string { + return strconv.FormatInt(o.Value, 10) +} + +// TypeName returns the name of the type. +func (o *Int) TypeName() string { + return "int" +} + +// BinaryOp returns another object that is the result of a given binary +// operator and a right-hand side object. +func (o *Int) BinaryOp(op token.Token, rhs Object) (Object, error) { + switch rhs := rhs.(type) { + case *Int: + switch op { + case token.Add: + r := o.Value + rhs.Value + if r == o.Value { + return o, nil + } + return &Int{Value: r}, nil + case token.Sub: + r := o.Value - rhs.Value + if r == o.Value { + return o, nil + } + return &Int{Value: r}, nil + case token.Mul: + r := o.Value * rhs.Value + if r == o.Value { + return o, nil + } + return &Int{Value: r}, nil + case token.Quo: + r := o.Value / rhs.Value + if r == o.Value { + return o, nil + } + return &Int{Value: r}, nil + case token.Rem: + r := o.Value % rhs.Value + if r == o.Value { + return o, nil + } + return &Int{Value: r}, nil + case token.And: + r := o.Value & rhs.Value + if r == o.Value { + return o, nil + } + return &Int{Value: r}, nil + case token.Or: + r := o.Value | rhs.Value + if r == o.Value { + return o, nil + } + return &Int{Value: r}, nil + case token.Xor: + r := o.Value ^ rhs.Value + if r == o.Value { + return o, nil + } + return &Int{Value: r}, nil + case token.AndNot: + r := o.Value &^ rhs.Value + if r == o.Value { + return o, nil + } + return &Int{Value: r}, nil + case token.Shl: + r := o.Value << uint64(rhs.Value) + if r == o.Value { + return o, nil + } + return &Int{Value: r}, nil + case token.Shr: + r := o.Value >> uint64(rhs.Value) + if r == o.Value { + return o, nil + } + return &Int{Value: r}, nil + case token.Less: + if o.Value < rhs.Value { + return TrueValue, nil + } + return FalseValue, nil + case token.Greater: + if o.Value > rhs.Value { + return TrueValue, nil + } + return FalseValue, nil + case token.LessEq: + if o.Value <= rhs.Value { + return TrueValue, nil + } + return FalseValue, nil + case token.GreaterEq: + if o.Value >= rhs.Value { + return TrueValue, nil + } + return FalseValue, nil + } + case *Float: + switch op { + case token.Add: + return &Float{Value: float64(o.Value) + rhs.Value}, nil + case token.Sub: + return &Float{Value: float64(o.Value) - rhs.Value}, nil + case token.Mul: + return &Float{Value: float64(o.Value) * rhs.Value}, nil + case token.Quo: + return &Float{Value: float64(o.Value) / rhs.Value}, nil + case token.Less: + if float64(o.Value) < rhs.Value { + return TrueValue, nil + } + return FalseValue, nil + case token.Greater: + if float64(o.Value) > rhs.Value { + return TrueValue, nil + } + return FalseValue, nil + case token.LessEq: + if float64(o.Value) <= rhs.Value { + return TrueValue, nil + } + return FalseValue, nil + case token.GreaterEq: + if float64(o.Value) >= rhs.Value { + return TrueValue, nil + } + return FalseValue, nil + } + case *Char: + switch op { + case token.Add: + return &Char{Value: rune(o.Value) + rhs.Value}, nil + case token.Sub: + return &Char{Value: rune(o.Value) - rhs.Value}, nil + case token.Less: + if o.Value < int64(rhs.Value) { + return TrueValue, nil + } + return FalseValue, nil + case token.Greater: + if o.Value > int64(rhs.Value) { + return TrueValue, nil + } + return FalseValue, nil + case token.LessEq: + if o.Value <= int64(rhs.Value) { + return TrueValue, nil + } + return FalseValue, nil + case token.GreaterEq: + if o.Value >= int64(rhs.Value) { + return TrueValue, nil + } + return FalseValue, nil + } + } + return nil, ErrInvalidOperator +} + +// Copy returns a copy of the type. +func (o *Int) Copy() Object { + return &Int{Value: o.Value} +} + +// IsFalsy returns true if the value of the type is falsy. +func (o *Int) IsFalsy() bool { + return o.Value == 0 +} + +// Equals returns true if the value of the type is equal to the value of +// another object. +func (o *Int) Equals(x Object) bool { + t, ok := x.(*Int) + if !ok { + return false + } + return o.Value == t.Value +} + +// Map represents a map of objects. +type Map struct { + ObjectImpl + Value map[string]Object +} + +// TypeName returns the name of the type. +func (o *Map) TypeName() string { + return "map" +} + +func (o *Map) String() string { + var pairs []string + for k, v := range o.Value { + pairs = append(pairs, fmt.Sprintf("%s: %s", k, v.String())) + } + return fmt.Sprintf("{%s}", strings.Join(pairs, ", ")) +} + +// Copy returns a copy of the type. +func (o *Map) Copy() Object { + c := make(map[string]Object) + for k, v := range o.Value { + c[k] = v.Copy() + } + return &Map{Value: c} +} + +// IsFalsy returns true if the value of the type is falsy. +func (o *Map) IsFalsy() bool { + return len(o.Value) == 0 +} + +// Equals returns true if the value of the type is equal to the value of +// another object. +func (o *Map) Equals(x Object) bool { + var xVal map[string]Object + switch x := x.(type) { + case *Map: + xVal = x.Value + case *ImmutableMap: + xVal = x.Value + default: + return false + } + if len(o.Value) != len(xVal) { + return false + } + for k, v := range o.Value { + tv := xVal[k] + if !v.Equals(tv) { + return false + } + } + return true +} + +// IndexGet returns the value for the given key. +func (o *Map) IndexGet(index Object) (res Object, err error) { + strIdx, ok := ToString(index) + if !ok { + err = ErrInvalidIndexType + return + } + res, ok = o.Value[strIdx] + if !ok { + res = UndefinedValue + } + return +} + +// IndexSet sets the value for the given key. +func (o *Map) IndexSet(index, value Object) (err error) { + strIdx, ok := ToString(index) + if !ok { + err = ErrInvalidIndexType + return + } + o.Value[strIdx] = value + return nil +} + +// Iterate creates a map iterator. +func (o *Map) Iterate() Iterator { + var keys []string + for k := range o.Value { + keys = append(keys, k) + } + return &MapIterator{ + v: o.Value, + k: keys, + l: len(keys), + } +} + +// CanIterate returns whether the Object can be Iterated. +func (o *Map) CanIterate() bool { + return true +} + +// ObjectPtr represents a free variable. +type ObjectPtr struct { + ObjectImpl + Value *Object +} + +func (o *ObjectPtr) String() string { + return "free-var" +} + +// TypeName returns the name of the type. +func (o *ObjectPtr) TypeName() string { + return "" +} + +// Copy returns a copy of the type. +func (o *ObjectPtr) Copy() Object { + return o +} + +// IsFalsy returns true if the value of the type is falsy. +func (o *ObjectPtr) IsFalsy() bool { + return o.Value == nil +} + +// Equals returns true if the value of the type is equal to the value of +// another object. +func (o *ObjectPtr) Equals(x Object) bool { + return o == x +} + +// String represents a string value. +type String struct { + ObjectImpl + Value string + runeStr []rune +} + +// TypeName returns the name of the type. +func (o *String) TypeName() string { + return "string" +} + +func (o *String) String() string { + return strconv.Quote(o.Value) +} + +// BinaryOp returns another object that is the result of a given binary +// operator and a right-hand side object. +func (o *String) BinaryOp(op token.Token, rhs Object) (Object, error) { + switch op { + case token.Add: + switch rhs := rhs.(type) { + case *String: + if len(o.Value)+len(rhs.Value) > MaxStringLen { + return nil, ErrStringLimit + } + return &String{Value: o.Value + rhs.Value}, nil + default: + rhsStr := rhs.String() + if len(o.Value)+len(rhsStr) > MaxStringLen { + return nil, ErrStringLimit + } + return &String{Value: o.Value + rhsStr}, nil + } + } + return nil, ErrInvalidOperator +} + +// IsFalsy returns true if the value of the type is falsy. +func (o *String) IsFalsy() bool { + return len(o.Value) == 0 +} + +// Copy returns a copy of the type. +func (o *String) Copy() Object { + return &String{Value: o.Value} +} + +// Equals returns true if the value of the type is equal to the value of +// another object. +func (o *String) Equals(x Object) bool { + t, ok := x.(*String) + if !ok { + return false + } + return o.Value == t.Value +} + +// IndexGet returns a character at a given index. +func (o *String) IndexGet(index Object) (res Object, err error) { + intIdx, ok := index.(*Int) + if !ok { + err = ErrInvalidIndexType + return + } + idxVal := int(intIdx.Value) + if o.runeStr == nil { + o.runeStr = []rune(o.Value) + } + if idxVal < 0 || idxVal >= len(o.runeStr) { + res = UndefinedValue + return + } + res = &Char{Value: o.runeStr[idxVal]} + return +} + +// Iterate creates a string iterator. +func (o *String) Iterate() Iterator { + if o.runeStr == nil { + o.runeStr = []rune(o.Value) + } + return &StringIterator{ + v: o.runeStr, + l: len(o.runeStr), + } +} + +// CanIterate returns whether the Object can be Iterated. +func (o *String) CanIterate() bool { + return true +} + +// Time represents a time value. +type Time struct { + ObjectImpl + Value time.Time +} + +func (o *Time) String() string { + return o.Value.String() +} + +// TypeName returns the name of the type. +func (o *Time) TypeName() string { + return "time" +} + +// BinaryOp returns another object that is the result of a given binary +// operator and a right-hand side object. +func (o *Time) BinaryOp(op token.Token, rhs Object) (Object, error) { + switch rhs := rhs.(type) { + case *Int: + switch op { + case token.Add: // time + int => time + if rhs.Value == 0 { + return o, nil + } + return &Time{Value: o.Value.Add(time.Duration(rhs.Value))}, nil + case token.Sub: // time - int => time + if rhs.Value == 0 { + return o, nil + } + return &Time{Value: o.Value.Add(time.Duration(-rhs.Value))}, nil + } + case *Time: + switch op { + case token.Sub: // time - time => int (duration) + return &Int{Value: int64(o.Value.Sub(rhs.Value))}, nil + case token.Less: // time < time => bool + if o.Value.Before(rhs.Value) { + return TrueValue, nil + } + return FalseValue, nil + case token.Greater: + if o.Value.After(rhs.Value) { + return TrueValue, nil + } + return FalseValue, nil + case token.LessEq: + if o.Value.Equal(rhs.Value) || o.Value.Before(rhs.Value) { + return TrueValue, nil + } + return FalseValue, nil + case token.GreaterEq: + if o.Value.Equal(rhs.Value) || o.Value.After(rhs.Value) { + return TrueValue, nil + } + return FalseValue, nil + } + } + return nil, ErrInvalidOperator +} + +// Copy returns a copy of the type. +func (o *Time) Copy() Object { + return &Time{Value: o.Value} +} + +// IsFalsy returns true if the value of the type is falsy. +func (o *Time) IsFalsy() bool { + return o.Value.IsZero() +} + +// Equals returns true if the value of the type is equal to the value of +// another object. +func (o *Time) Equals(x Object) bool { + t, ok := x.(*Time) + if !ok { + return false + } + return o.Value.Equal(t.Value) +} + +// Undefined represents an undefined value. +type Undefined struct { + ObjectImpl +} + +// TypeName returns the name of the type. +func (o *Undefined) TypeName() string { + return "undefined" +} + +func (o *Undefined) String() string { + return "" +} + +// Copy returns a copy of the type. +func (o *Undefined) Copy() Object { + return o +} + +// IsFalsy returns true if the value of the type is falsy. +func (o *Undefined) IsFalsy() bool { + return true +} + +// Equals returns true if the value of the type is equal to the value of +// another object. +func (o *Undefined) Equals(x Object) bool { + return o == x +} + +// IndexGet returns an element at a given index. +func (o *Undefined) IndexGet(_ Object) (Object, error) { + return UndefinedValue, nil +} + +// Iterate creates a map iterator. +func (o *Undefined) Iterate() Iterator { + return o +} + +// CanIterate returns whether the Object can be Iterated. +func (o *Undefined) CanIterate() bool { + return true +} + +// Next returns true if there are more elements to iterate. +func (o *Undefined) Next() bool { + return false +} + +// Key returns the key or index value of the current element. +func (o *Undefined) Key() Object { + return o +} + +// Value returns the value of the current element. +func (o *Undefined) Value() Object { + return o +} + +// UserFunction represents a user function. +type UserFunction struct { + ObjectImpl + Name string + Value CallableFunc + EncodingID string +} + +// TypeName returns the name of the type. +func (o *UserFunction) TypeName() string { + return "user-function:" + o.Name +} + +func (o *UserFunction) String() string { + return "" +} + +// Copy returns a copy of the type. +func (o *UserFunction) Copy() Object { + return &UserFunction{Value: o.Value} +} + +// Equals returns true if the value of the type is equal to the value of +// another object. +func (o *UserFunction) Equals(_ Object) bool { + return false +} + +// Call invokes a user function. +func (o *UserFunction) Call(args ...Object) (Object, error) { + return o.Value(args...) +} + +// CanCall returns whether the Object can be Called. +func (o *UserFunction) CanCall() bool { + return true +} diff --git a/objects/array.go b/objects/array.go deleted file mode 100644 index 1e917c5..0000000 --- a/objects/array.go +++ /dev/null @@ -1,130 +0,0 @@ -package objects - -import ( - "fmt" - "strings" - - "github.com/d5/tengo/compiler/token" -) - -// Array represents an array of objects. -type Array struct { - Value []Object -} - -// TypeName returns the name of the type. -func (o *Array) TypeName() string { - return "array" -} - -func (o *Array) String() string { - var elements []string - for _, e := range o.Value { - elements = append(elements, e.String()) - } - - return fmt.Sprintf("[%s]", strings.Join(elements, ", ")) -} - -// BinaryOp returns another object that is the result of -// a given binary operator and a right-hand side object. -func (o *Array) BinaryOp(op token.Token, rhs Object) (Object, error) { - if rhs, ok := rhs.(*Array); ok { - switch op { - case token.Add: - if len(rhs.Value) == 0 { - return o, nil - } - return &Array{Value: append(o.Value, rhs.Value...)}, nil - } - } - - return nil, ErrInvalidOperator -} - -// Copy returns a copy of the type. -func (o *Array) Copy() Object { - var c []Object - for _, elem := range o.Value { - c = append(c, elem.Copy()) - } - - return &Array{Value: c} -} - -// IsFalsy returns true if the value of the type is falsy. -func (o *Array) IsFalsy() bool { - return len(o.Value) == 0 -} - -// Equals returns true if the value of the type -// is equal to the value of another object. -func (o *Array) Equals(x Object) bool { - var xVal []Object - switch x := x.(type) { - case *Array: - xVal = x.Value - case *ImmutableArray: - xVal = x.Value - default: - return false - } - - if len(o.Value) != len(xVal) { - return false - } - - for i, e := range o.Value { - if !e.Equals(xVal[i]) { - return false - } - } - - return true -} - -// IndexGet returns an element at a given index. -func (o *Array) IndexGet(index Object) (res Object, err error) { - intIdx, ok := index.(*Int) - if !ok { - err = ErrInvalidIndexType - return - } - - idxVal := int(intIdx.Value) - - if idxVal < 0 || idxVal >= len(o.Value) { - res = UndefinedValue - return - } - - res = o.Value[idxVal] - - return -} - -// IndexSet sets an element at a given index. -func (o *Array) IndexSet(index, value Object) (err error) { - intIdx, ok := ToInt(index) - if !ok { - err = ErrInvalidIndexType - return - } - - if intIdx < 0 || intIdx >= len(o.Value) { - err = ErrIndexOutOfBounds - return - } - - o.Value[intIdx] = value - - return nil -} - -// Iterate creates an array iterator. -func (o *Array) Iterate() Iterator { - return &ArrayIterator{ - v: o.Value, - l: len(o.Value), - } -} diff --git a/objects/array_iterator.go b/objects/array_iterator.go deleted file mode 100644 index 204faa4..0000000 --- a/objects/array_iterator.go +++ /dev/null @@ -1,57 +0,0 @@ -package objects - -import "github.com/d5/tengo/compiler/token" - -// ArrayIterator is an iterator for an array. -type ArrayIterator struct { - v []Object - i int - l int -} - -// TypeName returns the name of the type. -func (i *ArrayIterator) TypeName() string { - return "array-iterator" -} - -func (i *ArrayIterator) String() string { - return "" -} - -// BinaryOp returns another object that is the result of -// a given binary operator and a right-hand side object. -func (i *ArrayIterator) BinaryOp(op token.Token, rhs Object) (Object, error) { - return nil, ErrInvalidOperator -} - -// IsFalsy returns true if the value of the type is falsy. -func (i *ArrayIterator) IsFalsy() bool { - return true -} - -// Equals returns true if the value of the type -// is equal to the value of another object. -func (i *ArrayIterator) Equals(Object) bool { - return false -} - -// Copy returns a copy of the type. -func (i *ArrayIterator) Copy() Object { - return &ArrayIterator{v: i.v, i: i.i, l: i.l} -} - -// Next returns true if there are more elements to iterate. -func (i *ArrayIterator) Next() bool { - i.i++ - return i.i <= i.l -} - -// Key returns the key or index value of the current element. -func (i *ArrayIterator) Key() Object { - return &Int{Value: int64(i.i - 1)} -} - -// Value returns the value of the current element. -func (i *ArrayIterator) Value() Object { - return i.v[i.i-1] -} diff --git a/objects/array_test.go b/objects/array_test.go deleted file mode 100644 index ce47411..0000000 --- a/objects/array_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package objects_test - -import ( - "testing" - - "github.com/d5/tengo/compiler/token" - "github.com/d5/tengo/objects" -) - -func TestArray_BinaryOp(t *testing.T) { - testBinaryOp(t, &objects.Array{Value: nil}, token.Add, &objects.Array{Value: nil}, &objects.Array{Value: nil}) - testBinaryOp(t, &objects.Array{Value: nil}, token.Add, &objects.Array{Value: []objects.Object{}}, &objects.Array{Value: nil}) - testBinaryOp(t, &objects.Array{Value: []objects.Object{}}, token.Add, &objects.Array{Value: nil}, &objects.Array{Value: []objects.Object{}}) - testBinaryOp(t, &objects.Array{Value: []objects.Object{}}, token.Add, &objects.Array{Value: []objects.Object{}}, &objects.Array{Value: []objects.Object{}}) - testBinaryOp(t, &objects.Array{Value: nil}, token.Add, &objects.Array{Value: []objects.Object{ - &objects.Int{Value: 1}, - }}, &objects.Array{Value: []objects.Object{ - &objects.Int{Value: 1}, - }}) - testBinaryOp(t, &objects.Array{Value: nil}, token.Add, &objects.Array{Value: []objects.Object{ - &objects.Int{Value: 1}, - &objects.Int{Value: 2}, - &objects.Int{Value: 3}, - }}, &objects.Array{Value: []objects.Object{ - &objects.Int{Value: 1}, - &objects.Int{Value: 2}, - &objects.Int{Value: 3}, - }}) - testBinaryOp(t, &objects.Array{Value: []objects.Object{ - &objects.Int{Value: 1}, - &objects.Int{Value: 2}, - &objects.Int{Value: 3}, - }}, token.Add, &objects.Array{Value: nil}, &objects.Array{Value: []objects.Object{ - &objects.Int{Value: 1}, - &objects.Int{Value: 2}, - &objects.Int{Value: 3}, - }}) - testBinaryOp(t, &objects.Array{Value: []objects.Object{ - &objects.Int{Value: 1}, - &objects.Int{Value: 2}, - &objects.Int{Value: 3}, - }}, token.Add, &objects.Array{Value: []objects.Object{ - &objects.Int{Value: 4}, - &objects.Int{Value: 5}, - &objects.Int{Value: 6}, - }}, &objects.Array{Value: []objects.Object{ - &objects.Int{Value: 1}, - &objects.Int{Value: 2}, - &objects.Int{Value: 3}, - &objects.Int{Value: 4}, - &objects.Int{Value: 5}, - &objects.Int{Value: 6}, - }}) -} diff --git a/objects/bool.go b/objects/bool.go deleted file mode 100644 index ac9949e..0000000 --- a/objects/bool.go +++ /dev/null @@ -1,64 +0,0 @@ -package objects - -import ( - "github.com/d5/tengo/compiler/token" -) - -// Bool represents a boolean value. -type Bool struct { - // this is intentionally non-public to force using objects.TrueValue and FalseValue always - value bool -} - -func (o *Bool) String() string { - if o.value { - return "true" - } - - return "false" -} - -// TypeName returns the name of the type. -func (o *Bool) TypeName() string { - return "bool" -} - -// BinaryOp returns another object that is the result of -// a given binary operator and a right-hand side object. -func (o *Bool) BinaryOp(op token.Token, rhs Object) (Object, error) { - return nil, ErrInvalidOperator -} - -// Copy returns a copy of the type. -func (o *Bool) Copy() Object { - return o -} - -// IsFalsy returns true if the value of the type is falsy. -func (o *Bool) IsFalsy() bool { - return !o.value -} - -// Equals returns true if the value of the type -// is equal to the value of another object. -func (o *Bool) Equals(x Object) bool { - return o == x -} - -// GobDecode decodes bool value from input bytes. -func (o *Bool) GobDecode(b []byte) (err error) { - o.value = b[0] == 1 - - return -} - -// GobEncode encodes bool values into bytes. -func (o *Bool) GobEncode() (b []byte, err error) { - if o.value { - b = []byte{1} - } else { - b = []byte{0} - } - - return -} diff --git a/objects/builtin_append.go b/objects/builtin_append.go deleted file mode 100644 index 9fb14b8..0000000 --- a/objects/builtin_append.go +++ /dev/null @@ -1,21 +0,0 @@ -package objects - -// append(arr, items...) -func builtinAppend(args ...Object) (Object, error) { - if len(args) < 2 { - return nil, ErrWrongNumArguments - } - - switch arg := args[0].(type) { - case *Array: - return &Array{Value: append(arg.Value, args[1:]...)}, nil - case *ImmutableArray: - return &Array{Value: append(arg.Value, args[1:]...)}, nil - default: - return nil, ErrInvalidArgumentType{ - Name: "first", - Expected: "array", - Found: arg.TypeName(), - } - } -} diff --git a/objects/builtin_convert.go b/objects/builtin_convert.go deleted file mode 100644 index b5f2d05..0000000 --- a/objects/builtin_convert.go +++ /dev/null @@ -1,169 +0,0 @@ -package objects - -import "github.com/d5/tengo" - -func builtinString(args ...Object) (Object, error) { - argsLen := len(args) - if !(argsLen == 1 || argsLen == 2) { - return nil, ErrWrongNumArguments - } - - if _, ok := args[0].(*String); ok { - return args[0], nil - } - - v, ok := ToString(args[0]) - if ok { - if len(v) > tengo.MaxStringLen { - return nil, ErrStringLimit - } - - return &String{Value: v}, nil - } - - if argsLen == 2 { - return args[1], nil - } - - return UndefinedValue, nil -} - -func builtinInt(args ...Object) (Object, error) { - argsLen := len(args) - if !(argsLen == 1 || argsLen == 2) { - return nil, ErrWrongNumArguments - } - - if _, ok := args[0].(*Int); ok { - return args[0], nil - } - - v, ok := ToInt64(args[0]) - if ok { - return &Int{Value: v}, nil - } - - if argsLen == 2 { - return args[1], nil - } - - return UndefinedValue, nil -} - -func builtinFloat(args ...Object) (Object, error) { - argsLen := len(args) - if !(argsLen == 1 || argsLen == 2) { - return nil, ErrWrongNumArguments - } - - if _, ok := args[0].(*Float); ok { - return args[0], nil - } - - v, ok := ToFloat64(args[0]) - if ok { - return &Float{Value: v}, nil - } - - if argsLen == 2 { - return args[1], nil - } - - return UndefinedValue, nil -} - -func builtinBool(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, ErrWrongNumArguments - } - - if _, ok := args[0].(*Bool); ok { - return args[0], nil - } - - v, ok := ToBool(args[0]) - if ok { - if v { - return TrueValue, nil - } - - return FalseValue, nil - } - - return UndefinedValue, nil -} - -func builtinChar(args ...Object) (Object, error) { - argsLen := len(args) - if !(argsLen == 1 || argsLen == 2) { - return nil, ErrWrongNumArguments - } - - if _, ok := args[0].(*Char); ok { - return args[0], nil - } - - v, ok := ToRune(args[0]) - if ok { - return &Char{Value: v}, nil - } - - if argsLen == 2 { - return args[1], nil - } - - return UndefinedValue, nil -} - -func builtinBytes(args ...Object) (Object, error) { - argsLen := len(args) - if !(argsLen == 1 || argsLen == 2) { - return nil, ErrWrongNumArguments - } - - // bytes(N) => create a new bytes with given size N - if n, ok := args[0].(*Int); ok { - if n.Value > int64(tengo.MaxBytesLen) { - return nil, ErrBytesLimit - } - - return &Bytes{Value: make([]byte, int(n.Value))}, nil - } - - v, ok := ToByteSlice(args[0]) - if ok { - if len(v) > tengo.MaxBytesLen { - return nil, ErrBytesLimit - } - - return &Bytes{Value: v}, nil - } - - if argsLen == 2 { - return args[1], nil - } - - return UndefinedValue, nil -} - -func builtinTime(args ...Object) (Object, error) { - argsLen := len(args) - if !(argsLen == 1 || argsLen == 2) { - return nil, ErrWrongNumArguments - } - - if _, ok := args[0].(*Time); ok { - return args[0], nil - } - - v, ok := ToTime(args[0]) - if ok { - return &Time{Value: v}, nil - } - - if argsLen == 2 { - return args[1], nil - } - - return UndefinedValue, nil -} diff --git a/objects/builtin_copy.go b/objects/builtin_copy.go deleted file mode 100644 index 4b254b2..0000000 --- a/objects/builtin_copy.go +++ /dev/null @@ -1,9 +0,0 @@ -package objects - -func builtinCopy(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, ErrWrongNumArguments - } - - return args[0].Copy(), nil -} diff --git a/objects/builtin_format.go b/objects/builtin_format.go deleted file mode 100644 index 1f0e75e..0000000 --- a/objects/builtin_format.go +++ /dev/null @@ -1,27 +0,0 @@ -package objects - -func builtinFormat(args ...Object) (Object, error) { - numArgs := len(args) - if numArgs == 0 { - return nil, ErrWrongNumArguments - } - - format, ok := args[0].(*String) - if !ok { - return nil, ErrInvalidArgumentType{ - Name: "format", - Expected: "string", - Found: args[0].TypeName(), - } - } - if numArgs == 1 { - return format, nil // okay to return 'format' directly as String is immutable - } - - s, err := Format(format.Value, args[1:]...) - if err != nil { - return nil, err - } - - return &String{Value: s}, nil -} diff --git a/objects/builtin_function.go b/objects/builtin_function.go deleted file mode 100644 index 1d02161..0000000 --- a/objects/builtin_function.go +++ /dev/null @@ -1,47 +0,0 @@ -package objects - -import ( - "github.com/d5/tengo/compiler/token" -) - -// BuiltinFunction represents a builtin function. -type BuiltinFunction struct { - Name string - Value CallableFunc -} - -// TypeName returns the name of the type. -func (o *BuiltinFunction) TypeName() string { - return "builtin-function:" + o.Name -} - -func (o *BuiltinFunction) String() string { - return "" -} - -// BinaryOp returns another object that is the result of -// a given binary operator and a right-hand side object. -func (o *BuiltinFunction) BinaryOp(op token.Token, rhs Object) (Object, error) { - return nil, ErrInvalidOperator -} - -// Copy returns a copy of the type. -func (o *BuiltinFunction) Copy() Object { - return &BuiltinFunction{Value: o.Value} -} - -// IsFalsy returns true if the value of the type is falsy. -func (o *BuiltinFunction) IsFalsy() bool { - return false -} - -// Equals returns true if the value of the type -// is equal to the value of another object. -func (o *BuiltinFunction) Equals(x Object) bool { - return false -} - -// Call executes a builtin function. -func (o *BuiltinFunction) Call(args ...Object) (Object, error) { - return o.Value(args...) -} diff --git a/objects/builtin_len.go b/objects/builtin_len.go deleted file mode 100644 index 39fbedd..0000000 --- a/objects/builtin_len.go +++ /dev/null @@ -1,29 +0,0 @@ -package objects - -// len(obj object) => int -func builtinLen(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, ErrWrongNumArguments - } - - switch arg := args[0].(type) { - case *Array: - return &Int{Value: int64(len(arg.Value))}, nil - case *ImmutableArray: - return &Int{Value: int64(len(arg.Value))}, nil - case *String: - return &Int{Value: int64(len(arg.Value))}, nil - case *Bytes: - return &Int{Value: int64(len(arg.Value))}, nil - case *Map: - return &Int{Value: int64(len(arg.Value))}, nil - case *ImmutableMap: - return &Int{Value: int64(len(arg.Value))}, nil - default: - return nil, ErrInvalidArgumentType{ - Name: "first", - Expected: "array/string/bytes/map", - Found: arg.TypeName(), - } - } -} diff --git a/objects/builtin_module.go b/objects/builtin_module.go deleted file mode 100644 index 0ad1d99..0000000 --- a/objects/builtin_module.go +++ /dev/null @@ -1,23 +0,0 @@ -package objects - -// BuiltinModule is an importable module that's written in Go. -type BuiltinModule struct { - Attrs map[string]Object -} - -// Import returns an immutable map for the module. -func (m *BuiltinModule) Import(moduleName string) (interface{}, error) { - return m.AsImmutableMap(moduleName), nil -} - -// AsImmutableMap converts builtin module into an immutable map. -func (m *BuiltinModule) AsImmutableMap(moduleName string) *ImmutableMap { - attrs := make(map[string]Object, len(m.Attrs)) - for k, v := range m.Attrs { - attrs[k] = v.Copy() - } - - attrs["__module_name__"] = &String{Value: moduleName} - - return &ImmutableMap{Value: attrs} -} diff --git a/objects/builtin_type.go b/objects/builtin_type.go deleted file mode 100644 index 376c26b..0000000 --- a/objects/builtin_type.go +++ /dev/null @@ -1,9 +0,0 @@ -package objects - -func builtinTypeName(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, ErrWrongNumArguments - } - - return &String{Value: args[0].TypeName()}, nil -} diff --git a/objects/builtin_type_checks.go b/objects/builtin_type_checks.go deleted file mode 100644 index d1e8471..0000000 --- a/objects/builtin_type_checks.go +++ /dev/null @@ -1,195 +0,0 @@ -package objects - -func builtinIsString(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, ErrWrongNumArguments - } - - if _, ok := args[0].(*String); ok { - return TrueValue, nil - } - - return FalseValue, nil -} - -func builtinIsInt(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, ErrWrongNumArguments - } - - if _, ok := args[0].(*Int); ok { - return TrueValue, nil - } - - return FalseValue, nil -} - -func builtinIsFloat(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, ErrWrongNumArguments - } - - if _, ok := args[0].(*Float); ok { - return TrueValue, nil - } - - return FalseValue, nil -} - -func builtinIsBool(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, ErrWrongNumArguments - } - - if _, ok := args[0].(*Bool); ok { - return TrueValue, nil - } - - return FalseValue, nil -} - -func builtinIsChar(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, ErrWrongNumArguments - } - - if _, ok := args[0].(*Char); ok { - return TrueValue, nil - } - - return FalseValue, nil -} - -func builtinIsBytes(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, ErrWrongNumArguments - } - - if _, ok := args[0].(*Bytes); ok { - return TrueValue, nil - } - - return FalseValue, nil -} - -func builtinIsArray(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, ErrWrongNumArguments - } - - if _, ok := args[0].(*Array); ok { - return TrueValue, nil - } - - return FalseValue, nil -} - -func builtinIsImmutableArray(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, ErrWrongNumArguments - } - - if _, ok := args[0].(*ImmutableArray); ok { - return TrueValue, nil - } - - return FalseValue, nil -} - -func builtinIsMap(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, ErrWrongNumArguments - } - - if _, ok := args[0].(*Map); ok { - return TrueValue, nil - } - - return FalseValue, nil -} - -func builtinIsImmutableMap(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, ErrWrongNumArguments - } - - if _, ok := args[0].(*ImmutableMap); ok { - return TrueValue, nil - } - - return FalseValue, nil -} - -func builtinIsTime(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, ErrWrongNumArguments - } - - if _, ok := args[0].(*Time); ok { - return TrueValue, nil - } - - return FalseValue, nil -} - -func builtinIsError(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, ErrWrongNumArguments - } - - if _, ok := args[0].(*Error); ok { - return TrueValue, nil - } - - return FalseValue, nil -} - -func builtinIsUndefined(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, ErrWrongNumArguments - } - - if args[0] == UndefinedValue { - return TrueValue, nil - } - - return FalseValue, nil -} - -func builtinIsFunction(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, ErrWrongNumArguments - } - - switch args[0].(type) { - case *CompiledFunction, *Closure: - return TrueValue, nil - } - - return FalseValue, nil -} - -func builtinIsCallable(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, ErrWrongNumArguments - } - - switch args[0].(type) { - case *CompiledFunction, *Closure, Callable: // BuiltinFunction is Callable - return TrueValue, nil - } - - return FalseValue, nil -} - -func builtinIsIterable(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, ErrWrongNumArguments - } - - if _, ok := args[0].(Iterable); ok { - return TrueValue, nil - } - - return FalseValue, nil -} diff --git a/objects/builtins.go b/objects/builtins.go deleted file mode 100644 index 773636e..0000000 --- a/objects/builtins.go +++ /dev/null @@ -1,118 +0,0 @@ -package objects - -// Builtins contains all default builtin functions. -// Use GetBuiltinFunctions instead of accessing Builtins directly. -var Builtins = []*BuiltinFunction{ - { - Name: "len", - Value: builtinLen, - }, - { - Name: "copy", - Value: builtinCopy, - }, - { - Name: "append", - Value: builtinAppend, - }, - { - Name: "string", - Value: builtinString, - }, - { - Name: "int", - Value: builtinInt, - }, - { - Name: "bool", - Value: builtinBool, - }, - { - Name: "float", - Value: builtinFloat, - }, - { - Name: "char", - Value: builtinChar, - }, - { - Name: "bytes", - Value: builtinBytes, - }, - { - Name: "time", - Value: builtinTime, - }, - { - Name: "is_int", - Value: builtinIsInt, - }, - { - Name: "is_float", - Value: builtinIsFloat, - }, - { - Name: "is_string", - Value: builtinIsString, - }, - { - Name: "is_bool", - Value: builtinIsBool, - }, - { - Name: "is_char", - Value: builtinIsChar, - }, - { - Name: "is_bytes", - Value: builtinIsBytes, - }, - { - Name: "is_array", - Value: builtinIsArray, - }, - { - Name: "is_immutable_array", - Value: builtinIsImmutableArray, - }, - { - Name: "is_map", - Value: builtinIsMap, - }, - { - Name: "is_immutable_map", - Value: builtinIsImmutableMap, - }, - { - Name: "is_iterable", - Value: builtinIsIterable, - }, - { - Name: "is_time", - Value: builtinIsTime, - }, - { - Name: "is_error", - Value: builtinIsError, - }, - { - Name: "is_undefined", - Value: builtinIsUndefined, - }, - { - Name: "is_function", - Value: builtinIsFunction, - }, - { - Name: "is_callable", - Value: builtinIsCallable, - }, - { - Name: "type_name", - Value: builtinTypeName, - }, - { - Name: "format", - Value: builtinFormat, - }, -} diff --git a/objects/bytes.go b/objects/bytes.go deleted file mode 100644 index 5159c22..0000000 --- a/objects/bytes.go +++ /dev/null @@ -1,89 +0,0 @@ -package objects - -import ( - "bytes" - - "github.com/d5/tengo" - "github.com/d5/tengo/compiler/token" -) - -// Bytes represents a byte array. -type Bytes struct { - Value []byte -} - -func (o *Bytes) String() string { - return string(o.Value) -} - -// TypeName returns the name of the type. -func (o *Bytes) TypeName() string { - return "bytes" -} - -// BinaryOp returns another object that is the result of -// a given binary operator and a right-hand side object. -func (o *Bytes) BinaryOp(op token.Token, rhs Object) (Object, error) { - switch op { - case token.Add: - switch rhs := rhs.(type) { - case *Bytes: - if len(o.Value)+len(rhs.Value) > tengo.MaxBytesLen { - return nil, ErrBytesLimit - } - - return &Bytes{Value: append(o.Value, rhs.Value...)}, nil - } - } - - return nil, ErrInvalidOperator -} - -// Copy returns a copy of the type. -func (o *Bytes) Copy() Object { - return &Bytes{Value: append([]byte{}, o.Value...)} -} - -// IsFalsy returns true if the value of the type is falsy. -func (o *Bytes) IsFalsy() bool { - return len(o.Value) == 0 -} - -// Equals returns true if the value of the type -// is equal to the value of another object. -func (o *Bytes) Equals(x Object) bool { - t, ok := x.(*Bytes) - if !ok { - return false - } - - return bytes.Equal(o.Value, t.Value) -} - -// IndexGet returns an element (as Int) at a given index. -func (o *Bytes) IndexGet(index Object) (res Object, err error) { - intIdx, ok := index.(*Int) - if !ok { - err = ErrInvalidIndexType - return - } - - idxVal := int(intIdx.Value) - - if idxVal < 0 || idxVal >= len(o.Value) { - res = UndefinedValue - return - } - - res = &Int{Value: int64(o.Value[idxVal])} - - return -} - -// Iterate creates a bytes iterator. -func (o *Bytes) Iterate() Iterator { - return &BytesIterator{ - v: o.Value, - l: len(o.Value), - } -} diff --git a/objects/bytes_iterator.go b/objects/bytes_iterator.go deleted file mode 100644 index 18a36e1..0000000 --- a/objects/bytes_iterator.go +++ /dev/null @@ -1,57 +0,0 @@ -package objects - -import "github.com/d5/tengo/compiler/token" - -// BytesIterator represents an iterator for a string. -type BytesIterator struct { - v []byte - i int - l int -} - -// TypeName returns the name of the type. -func (i *BytesIterator) TypeName() string { - return "bytes-iterator" -} - -func (i *BytesIterator) String() string { - return "" -} - -// BinaryOp returns another object that is the result of -// a given binary operator and a right-hand side object. -func (i *BytesIterator) BinaryOp(op token.Token, rhs Object) (Object, error) { - return nil, ErrInvalidOperator -} - -// IsFalsy returns true if the value of the type is falsy. -func (i *BytesIterator) IsFalsy() bool { - return true -} - -// Equals returns true if the value of the type -// is equal to the value of another object. -func (i *BytesIterator) Equals(Object) bool { - return false -} - -// Copy returns a copy of the type. -func (i *BytesIterator) Copy() Object { - return &BytesIterator{v: i.v, i: i.i, l: i.l} -} - -// Next returns true if there are more elements to iterate. -func (i *BytesIterator) Next() bool { - i.i++ - return i.i <= i.l -} - -// Key returns the key or index value of the current element. -func (i *BytesIterator) Key() Object { - return &Int{Value: int64(i.i - 1)} -} - -// Value returns the value of the current element. -func (i *BytesIterator) Value() Object { - return &Int{Value: int64(i.v[i.i-1])} -} diff --git a/objects/callable.go b/objects/callable.go deleted file mode 100644 index a066e1b..0000000 --- a/objects/callable.go +++ /dev/null @@ -1,9 +0,0 @@ -package objects - -// Callable represents an object that can be called like a function. -type Callable interface { - // Call should take an arbitrary number of arguments - // and returns a return value and/or an error, - // which the VM will consider as a run-time error. - Call(args ...Object) (ret Object, err error) -} diff --git a/objects/callable_func.go b/objects/callable_func.go deleted file mode 100644 index ad25e65..0000000 --- a/objects/callable_func.go +++ /dev/null @@ -1,4 +0,0 @@ -package objects - -// CallableFunc is a function signature for the callable functions. -type CallableFunc = func(args ...Object) (ret Object, err error) diff --git a/objects/char.go b/objects/char.go deleted file mode 100644 index 4458bd1..0000000 --- a/objects/char.go +++ /dev/null @@ -1,119 +0,0 @@ -package objects - -import ( - "github.com/d5/tengo/compiler/token" -) - -// Char represents a character value. -type Char struct { - Value rune -} - -func (o *Char) String() string { - return string(o.Value) -} - -// TypeName returns the name of the type. -func (o *Char) TypeName() string { - return "char" -} - -// BinaryOp returns another object that is the result of -// a given binary operator and a right-hand side object. -func (o *Char) BinaryOp(op token.Token, rhs Object) (Object, error) { - switch rhs := rhs.(type) { - case *Char: - switch op { - case token.Add: - r := o.Value + rhs.Value - if r == o.Value { - return o, nil - } - return &Char{Value: r}, nil - case token.Sub: - r := o.Value - rhs.Value - if r == o.Value { - return o, nil - } - return &Char{Value: r}, nil - case token.Less: - if o.Value < rhs.Value { - return TrueValue, nil - } - return FalseValue, nil - case token.Greater: - if o.Value > rhs.Value { - return TrueValue, nil - } - return FalseValue, nil - case token.LessEq: - if o.Value <= rhs.Value { - return TrueValue, nil - } - return FalseValue, nil - case token.GreaterEq: - if o.Value >= rhs.Value { - return TrueValue, nil - } - return FalseValue, nil - } - case *Int: - switch op { - case token.Add: - r := o.Value + rune(rhs.Value) - if r == o.Value { - return o, nil - } - return &Char{Value: r}, nil - case token.Sub: - r := o.Value - rune(rhs.Value) - if r == o.Value { - return o, nil - } - return &Char{Value: r}, nil - case token.Less: - if int64(o.Value) < rhs.Value { - return TrueValue, nil - } - return FalseValue, nil - case token.Greater: - if int64(o.Value) > rhs.Value { - return TrueValue, nil - } - return FalseValue, nil - case token.LessEq: - if int64(o.Value) <= rhs.Value { - return TrueValue, nil - } - return FalseValue, nil - case token.GreaterEq: - if int64(o.Value) >= rhs.Value { - return TrueValue, nil - } - return FalseValue, nil - } - } - - return nil, ErrInvalidOperator -} - -// Copy returns a copy of the type. -func (o *Char) Copy() Object { - return &Char{Value: o.Value} -} - -// IsFalsy returns true if the value of the type is falsy. -func (o *Char) IsFalsy() bool { - return o.Value == 0 -} - -// Equals returns true if the value of the type -// is equal to the value of another object. -func (o *Char) Equals(x Object) bool { - t, ok := x.(*Char) - if !ok { - return false - } - - return o.Value == t.Value -} diff --git a/objects/closure.go b/objects/closure.go deleted file mode 100644 index 06058b2..0000000 --- a/objects/closure.go +++ /dev/null @@ -1,45 +0,0 @@ -package objects - -import ( - "github.com/d5/tengo/compiler/token" -) - -// Closure represents a function closure. -type Closure struct { - Fn *CompiledFunction - Free []*ObjectPtr -} - -// TypeName returns the name of the type. -func (o *Closure) TypeName() string { - return "closure" -} - -func (o *Closure) String() string { - return "" -} - -// BinaryOp returns another object that is the result of -// a given binary operator and a right-hand side object. -func (o *Closure) BinaryOp(op token.Token, rhs Object) (Object, error) { - return nil, ErrInvalidOperator -} - -// Copy returns a copy of the type. -func (o *Closure) Copy() Object { - return &Closure{ - Fn: o.Fn.Copy().(*CompiledFunction), - Free: append([]*ObjectPtr{}, o.Free...), // DO NOT Copy() of elements; these are variable pointers - } -} - -// IsFalsy returns true if the value of the type is falsy. -func (o *Closure) IsFalsy() bool { - return false -} - -// Equals returns true if the value of the type -// is equal to the value of another object. -func (o *Closure) Equals(x Object) bool { - return false -} diff --git a/objects/compiled_function.go b/objects/compiled_function.go deleted file mode 100644 index d42e69e..0000000 --- a/objects/compiled_function.go +++ /dev/null @@ -1,62 +0,0 @@ -package objects - -import ( - "github.com/d5/tengo/compiler/source" - "github.com/d5/tengo/compiler/token" -) - -// CompiledFunction represents a compiled function. -type CompiledFunction struct { - Instructions []byte - NumLocals int // number of local variables (including function parameters) - NumParameters int - VarArgs bool - SourceMap map[int]source.Pos -} - -// TypeName returns the name of the type. -func (o *CompiledFunction) TypeName() string { - return "compiled-function" -} - -func (o *CompiledFunction) String() string { - return "" -} - -// BinaryOp returns another object that is the result of -// a given binary operator and a right-hand side object. -func (o *CompiledFunction) BinaryOp(op token.Token, rhs Object) (Object, error) { - return nil, ErrInvalidOperator -} - -// Copy returns a copy of the type. -func (o *CompiledFunction) Copy() Object { - return &CompiledFunction{ - Instructions: append([]byte{}, o.Instructions...), - NumLocals: o.NumLocals, - NumParameters: o.NumParameters, - VarArgs: o.VarArgs, - } -} - -// IsFalsy returns true if the value of the type is falsy. -func (o *CompiledFunction) IsFalsy() bool { - return false -} - -// Equals returns true if the value of the type -// is equal to the value of another object. -func (o *CompiledFunction) Equals(x Object) bool { - return false -} - -// SourcePos returns the source position of the instruction at ip. -func (o *CompiledFunction) SourcePos(ip int) source.Pos { - for ip >= 0 { - if p, ok := o.SourceMap[ip]; ok { - return p - } - ip-- - } - return source.NoPos -} diff --git a/objects/conversion.go b/objects/conversion.go deleted file mode 100644 index 2751413..0000000 --- a/objects/conversion.go +++ /dev/null @@ -1,276 +0,0 @@ -package objects - -import ( - "errors" - "fmt" - "strconv" - "time" - - "github.com/d5/tengo" -) - -// ToString will try to convert object o to string value. -func ToString(o Object) (v string, ok bool) { - if o == UndefinedValue { - //ok = false - return - } - - ok = true - - if str, isStr := o.(*String); isStr { - v = str.Value - } else { - v = o.String() - } - - return -} - -// ToInt will try to convert object o to int value. -func ToInt(o Object) (v int, ok bool) { - switch o := o.(type) { - case *Int: - v = int(o.Value) - ok = true - case *Float: - v = int(o.Value) - ok = true - case *Char: - v = int(o.Value) - ok = true - case *Bool: - if o == TrueValue { - v = 1 - } - ok = true - case *String: - c, err := strconv.ParseInt(o.Value, 10, 64) - if err == nil { - v = int(c) - ok = true - } - } - - //ok = false - return -} - -// ToInt64 will try to convert object o to int64 value. -func ToInt64(o Object) (v int64, ok bool) { - switch o := o.(type) { - case *Int: - v = o.Value - ok = true - case *Float: - v = int64(o.Value) - ok = true - case *Char: - v = int64(o.Value) - ok = true - case *Bool: - if o == TrueValue { - v = 1 - } - ok = true - case *String: - c, err := strconv.ParseInt(o.Value, 10, 64) - if err == nil { - v = c - ok = true - } - } - - //ok = false - return -} - -// ToFloat64 will try to convert object o to float64 value. -func ToFloat64(o Object) (v float64, ok bool) { - switch o := o.(type) { - case *Int: - v = float64(o.Value) - ok = true - case *Float: - v = o.Value - ok = true - case *String: - c, err := strconv.ParseFloat(o.Value, 64) - if err == nil { - v = c - ok = true - } - } - - //ok = false - return -} - -// ToBool will try to convert object o to bool value. -func ToBool(o Object) (v bool, ok bool) { - ok = true - v = !o.IsFalsy() - - return -} - -// ToRune will try to convert object o to rune value. -func ToRune(o Object) (v rune, ok bool) { - switch o := o.(type) { - case *Int: - v = rune(o.Value) - ok = true - case *Char: - v = rune(o.Value) - ok = true - } - - //ok = false - return -} - -// ToByteSlice will try to convert object o to []byte value. -func ToByteSlice(o Object) (v []byte, ok bool) { - switch o := o.(type) { - case *Bytes: - v = o.Value - ok = true - case *String: - v = []byte(o.Value) - ok = true - } - - //ok = false - return -} - -// ToTime will try to convert object o to time.Time value. -func ToTime(o Object) (v time.Time, ok bool) { - switch o := o.(type) { - case *Time: - v = o.Value - ok = true - case *Int: - v = time.Unix(o.Value, 0) - ok = true - } - - //ok = false - return -} - -// ToInterface attempts to convert an object o to an interface{} value -func ToInterface(o Object) (res interface{}) { - switch o := o.(type) { - case *Int: - res = o.Value - case *String: - res = o.Value - case *Float: - res = o.Value - case *Bool: - res = o == TrueValue - case *Char: - res = o.Value - case *Bytes: - res = o.Value - case *Array: - res = make([]interface{}, len(o.Value)) - for i, val := range o.Value { - res.([]interface{})[i] = ToInterface(val) - } - case *ImmutableArray: - res = make([]interface{}, len(o.Value)) - for i, val := range o.Value { - res.([]interface{})[i] = ToInterface(val) - } - case *Map: - res = make(map[string]interface{}) - for key, v := range o.Value { - res.(map[string]interface{})[key] = ToInterface(v) - } - case *ImmutableMap: - res = make(map[string]interface{}) - for key, v := range o.Value { - res.(map[string]interface{})[key] = ToInterface(v) - } - case *Time: - res = o.Value - case *Error: - res = errors.New(o.String()) - case *Undefined: - res = nil - case Object: - return o - } - - return -} - -// FromInterface will attempt to convert an interface{} v to a Tengo Object -func FromInterface(v interface{}) (Object, error) { - switch v := v.(type) { - case nil: - return UndefinedValue, nil - case string: - if len(v) > tengo.MaxStringLen { - return nil, ErrStringLimit - } - return &String{Value: v}, nil - case int64: - return &Int{Value: v}, nil - case int: - return &Int{Value: int64(v)}, nil - case bool: - if v { - return TrueValue, nil - } - return FalseValue, nil - case rune: - return &Char{Value: v}, nil - case byte: - return &Char{Value: rune(v)}, nil - case float64: - return &Float{Value: v}, nil - case []byte: - if len(v) > tengo.MaxBytesLen { - return nil, ErrBytesLimit - } - return &Bytes{Value: v}, nil - case error: - return &Error{Value: &String{Value: v.Error()}}, nil - case map[string]Object: - return &Map{Value: v}, nil - case map[string]interface{}: - kv := make(map[string]Object) - for vk, vv := range v { - vo, err := FromInterface(vv) - if err != nil { - return nil, err - } - kv[vk] = vo - } - return &Map{Value: kv}, nil - case []Object: - return &Array{Value: v}, nil - case []interface{}: - arr := make([]Object, len(v)) - for i, e := range v { - vo, err := FromInterface(e) - if err != nil { - return nil, err - } - - arr[i] = vo - } - return &Array{Value: arr}, nil - case time.Time: - return &Time{Value: v}, nil - case Object: - return v, nil - case CallableFunc: - return &UserFunction{Value: v}, nil - } - - return nil, fmt.Errorf("cannot convert to object: %T", v) -} diff --git a/objects/count_objects.go b/objects/count_objects.go deleted file mode 100644 index 8c482eb..0000000 --- a/objects/count_objects.go +++ /dev/null @@ -1,31 +0,0 @@ -package objects - -// CountObjects returns the number of objects that a given object o contains. -// For scalar value types, it will always be 1. For compound value types, -// this will include its elements and all of their elements recursively. -func CountObjects(o Object) (c int) { - c = 1 - - switch o := o.(type) { - case *Array: - for _, v := range o.Value { - c += CountObjects(v) - } - case *ImmutableArray: - for _, v := range o.Value { - c += CountObjects(v) - } - case *Map: - for _, v := range o.Value { - c += CountObjects(v) - } - case *ImmutableMap: - for _, v := range o.Value { - c += CountObjects(v) - } - case *Error: - c += CountObjects(o.Value) - } - - return -} diff --git a/objects/count_objects_test.go b/objects/count_objects_test.go deleted file mode 100644 index f2427c9..0000000 --- a/objects/count_objects_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package objects_test - -import ( - "testing" - "time" - - "github.com/d5/tengo/assert" - "github.com/d5/tengo/objects" -) - -func TestNumObjects(t *testing.T) { - testCountObjects(t, &objects.Array{}, 1) - testCountObjects(t, &objects.Array{Value: []objects.Object{ - &objects.Int{Value: 1}, - &objects.Int{Value: 2}, - &objects.Array{Value: []objects.Object{ - &objects.Int{Value: 3}, - &objects.Int{Value: 4}, - &objects.Int{Value: 5}, - }}, - }}, 7) - testCountObjects(t, objects.TrueValue, 1) - testCountObjects(t, objects.FalseValue, 1) - testCountObjects(t, &objects.BuiltinFunction{}, 1) - testCountObjects(t, &objects.Bytes{Value: []byte("foobar")}, 1) - testCountObjects(t, &objects.Char{Value: '가'}, 1) - testCountObjects(t, &objects.Closure{}, 1) - testCountObjects(t, &objects.CompiledFunction{}, 1) - testCountObjects(t, &objects.Error{Value: &objects.Int{Value: 5}}, 2) - testCountObjects(t, &objects.Float{Value: 19.84}, 1) - testCountObjects(t, &objects.ImmutableArray{Value: []objects.Object{ - &objects.Int{Value: 1}, - &objects.Int{Value: 2}, - &objects.ImmutableArray{Value: []objects.Object{ - &objects.Int{Value: 3}, - &objects.Int{Value: 4}, - &objects.Int{Value: 5}, - }}, - }}, 7) - testCountObjects(t, &objects.ImmutableMap{Value: map[string]objects.Object{ - "k1": &objects.Int{Value: 1}, - "k2": &objects.Int{Value: 2}, - "k3": &objects.Array{Value: []objects.Object{ - &objects.Int{Value: 3}, - &objects.Int{Value: 4}, - &objects.Int{Value: 5}, - }}, - }}, 7) - testCountObjects(t, &objects.Int{Value: 1984}, 1) - testCountObjects(t, &objects.Map{Value: map[string]objects.Object{ - "k1": &objects.Int{Value: 1}, - "k2": &objects.Int{Value: 2}, - "k3": &objects.Array{Value: []objects.Object{ - &objects.Int{Value: 3}, - &objects.Int{Value: 4}, - &objects.Int{Value: 5}, - }}, - }}, 7) - testCountObjects(t, &objects.String{Value: "foo bar"}, 1) - testCountObjects(t, &objects.Time{Value: time.Now()}, 1) - testCountObjects(t, objects.UndefinedValue, 1) -} - -func testCountObjects(t *testing.T, o objects.Object, expected int) { - assert.Equal(t, expected, objects.CountObjects(o)) -} diff --git a/objects/error.go b/objects/error.go deleted file mode 100644 index be21de0..0000000 --- a/objects/error.go +++ /dev/null @@ -1,47 +0,0 @@ -package objects - -import ( - "fmt" - - "github.com/d5/tengo/compiler/token" -) - -// Error represents a string value. -type Error struct { - Value Object -} - -// TypeName returns the name of the type. -func (o *Error) TypeName() string { - return "error" -} - -func (o *Error) String() string { - if o.Value != nil { - return fmt.Sprintf("error: %s", o.Value.String()) - } - - return "error" -} - -// BinaryOp returns another object that is the result of -// a given binary operator and a right-hand side object. -func (o *Error) BinaryOp(op token.Token, rhs Object) (Object, error) { - return nil, ErrInvalidOperator -} - -// IsFalsy returns true if the value of the type is falsy. -func (o *Error) IsFalsy() bool { - return true // error is always false. -} - -// Copy returns a copy of the type. -func (o *Error) Copy() Object { - return &Error{Value: o.Value.Copy()} -} - -// Equals returns true if the value of the type -// is equal to the value of another object. -func (o *Error) Equals(x Object) bool { - return o == x // pointer equality -} diff --git a/objects/error_test.go b/objects/error_test.go deleted file mode 100644 index db2d3d0..0000000 --- a/objects/error_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package objects_test - -import ( - "testing" - - "github.com/d5/tengo/assert" - "github.com/d5/tengo/objects" -) - -func TestError_Equals(t *testing.T) { - err1 := &objects.Error{Value: &objects.String{Value: "some error"}} - err2 := err1 - assert.True(t, err1.Equals(err2)) - assert.True(t, err2.Equals(err1)) - - err2 = &objects.Error{Value: &objects.String{Value: "some error"}} - assert.False(t, err1.Equals(err2)) - assert.False(t, err2.Equals(err1)) -} diff --git a/objects/errors.go b/objects/errors.go deleted file mode 100644 index bcd480a..0000000 --- a/objects/errors.go +++ /dev/null @@ -1,38 +0,0 @@ -package objects - -import ( - "errors" - "fmt" -) - -// ErrIndexOutOfBounds is an error where a given index is out of the bounds. -var ErrIndexOutOfBounds = errors.New("index out of bounds") - -// ErrInvalidIndexType represents an invalid index type. -var ErrInvalidIndexType = errors.New("invalid index type") - -// ErrInvalidIndexValueType represents an invalid index value type. -var ErrInvalidIndexValueType = errors.New("invalid index value type") - -// ErrInvalidOperator represents an error for invalid operator usage. -var ErrInvalidOperator = errors.New("invalid operator") - -// ErrWrongNumArguments represents a wrong number of arguments error. -var ErrWrongNumArguments = errors.New("wrong number of arguments") - -// ErrBytesLimit represents an error where the size of bytes value exceeds the limit. -var ErrBytesLimit = errors.New("exceeding bytes size limit") - -// ErrStringLimit represents an error where the size of string value exceeds the limit. -var ErrStringLimit = errors.New("exceeding string size limit") - -// ErrInvalidArgumentType represents an invalid argument value type error. -type ErrInvalidArgumentType struct { - Name string - Expected string - Found string -} - -func (e ErrInvalidArgumentType) Error() string { - return fmt.Sprintf("invalid type for argument '%s': expected %s, found %s", e.Name, e.Expected, e.Found) -} diff --git a/objects/float.go b/objects/float.go deleted file mode 100644 index 6599730..0000000 --- a/objects/float.go +++ /dev/null @@ -1,146 +0,0 @@ -package objects - -import ( - "math" - "strconv" - - "github.com/d5/tengo/compiler/token" -) - -// Float represents a floating point number value. -type Float struct { - Value float64 -} - -func (o *Float) String() string { - return strconv.FormatFloat(o.Value, 'f', -1, 64) -} - -// TypeName returns the name of the type. -func (o *Float) TypeName() string { - return "float" -} - -// BinaryOp returns another object that is the result of -// a given binary operator and a right-hand side object. -func (o *Float) BinaryOp(op token.Token, rhs Object) (Object, error) { - switch rhs := rhs.(type) { - case *Float: - switch op { - case token.Add: - r := o.Value + rhs.Value - if r == o.Value { - return o, nil - } - return &Float{Value: r}, nil - case token.Sub: - r := o.Value - rhs.Value - if r == o.Value { - return o, nil - } - return &Float{Value: r}, nil - case token.Mul: - r := o.Value * rhs.Value - if r == o.Value { - return o, nil - } - return &Float{Value: r}, nil - case token.Quo: - r := o.Value / rhs.Value - if r == o.Value { - return o, nil - } - return &Float{Value: r}, nil - case token.Less: - if o.Value < rhs.Value { - return TrueValue, nil - } - return FalseValue, nil - case token.Greater: - if o.Value > rhs.Value { - return TrueValue, nil - } - return FalseValue, nil - case token.LessEq: - if o.Value <= rhs.Value { - return TrueValue, nil - } - return FalseValue, nil - case token.GreaterEq: - if o.Value >= rhs.Value { - return TrueValue, nil - } - return FalseValue, nil - } - case *Int: - switch op { - case token.Add: - r := o.Value + float64(rhs.Value) - if r == o.Value { - return o, nil - } - return &Float{Value: r}, nil - case token.Sub: - r := o.Value - float64(rhs.Value) - if r == o.Value { - return o, nil - } - return &Float{Value: r}, nil - case token.Mul: - r := o.Value * float64(rhs.Value) - if r == o.Value { - return o, nil - } - return &Float{Value: r}, nil - case token.Quo: - r := o.Value / float64(rhs.Value) - if r == o.Value { - return o, nil - } - return &Float{Value: r}, nil - case token.Less: - if o.Value < float64(rhs.Value) { - return TrueValue, nil - } - return FalseValue, nil - case token.Greater: - if o.Value > float64(rhs.Value) { - return TrueValue, nil - } - return FalseValue, nil - case token.LessEq: - if o.Value <= float64(rhs.Value) { - return TrueValue, nil - } - return FalseValue, nil - case token.GreaterEq: - if o.Value >= float64(rhs.Value) { - return TrueValue, nil - } - return FalseValue, nil - } - } - - return nil, ErrInvalidOperator -} - -// Copy returns a copy of the type. -func (o *Float) Copy() Object { - return &Float{Value: o.Value} -} - -// IsFalsy returns true if the value of the type is falsy. -func (o *Float) IsFalsy() bool { - return math.IsNaN(o.Value) -} - -// Equals returns true if the value of the type -// is equal to the value of another object. -func (o *Float) Equals(x Object) bool { - t, ok := x.(*Float) - if !ok { - return false - } - - return o.Value == t.Value -} diff --git a/objects/float_test.go b/objects/float_test.go deleted file mode 100644 index c7f89c2..0000000 --- a/objects/float_test.go +++ /dev/null @@ -1,126 +0,0 @@ -package objects_test - -import ( - "testing" - - "github.com/d5/tengo/compiler/token" - "github.com/d5/tengo/objects" -) - -func TestFloat_BinaryOp(t *testing.T) { - // float + float - for l := float64(-2); l <= 2.1; l += 0.4 { - for r := float64(-2); r <= 2.1; r += 0.4 { - testBinaryOp(t, &objects.Float{Value: l}, token.Add, &objects.Float{Value: r}, &objects.Float{Value: l + r}) - } - } - - // float - float - for l := float64(-2); l <= 2.1; l += 0.4 { - for r := float64(-2); r <= 2.1; r += 0.4 { - testBinaryOp(t, &objects.Float{Value: l}, token.Sub, &objects.Float{Value: r}, &objects.Float{Value: l - r}) - } - } - - // float * float - for l := float64(-2); l <= 2.1; l += 0.4 { - for r := float64(-2); r <= 2.1; r += 0.4 { - testBinaryOp(t, &objects.Float{Value: l}, token.Mul, &objects.Float{Value: r}, &objects.Float{Value: l * r}) - } - } - - // float / float - for l := float64(-2); l <= 2.1; l += 0.4 { - for r := float64(-2); r <= 2.1; r += 0.4 { - if r != 0 { - testBinaryOp(t, &objects.Float{Value: l}, token.Quo, &objects.Float{Value: r}, &objects.Float{Value: l / r}) - } - } - } - - // float < float - for l := float64(-2); l <= 2.1; l += 0.4 { - for r := float64(-2); r <= 2.1; r += 0.4 { - testBinaryOp(t, &objects.Float{Value: l}, token.Less, &objects.Float{Value: r}, boolValue(l < r)) - } - } - - // float > float - for l := float64(-2); l <= 2.1; l += 0.4 { - for r := float64(-2); r <= 2.1; r += 0.4 { - testBinaryOp(t, &objects.Float{Value: l}, token.Greater, &objects.Float{Value: r}, boolValue(l > r)) - } - } - - // float <= float - for l := float64(-2); l <= 2.1; l += 0.4 { - for r := float64(-2); r <= 2.1; r += 0.4 { - testBinaryOp(t, &objects.Float{Value: l}, token.LessEq, &objects.Float{Value: r}, boolValue(l <= r)) - } - } - - // float >= float - for l := float64(-2); l <= 2.1; l += 0.4 { - for r := float64(-2); r <= 2.1; r += 0.4 { - testBinaryOp(t, &objects.Float{Value: l}, token.GreaterEq, &objects.Float{Value: r}, boolValue(l >= r)) - } - } - - // float + int - for l := float64(-2); l <= 2.1; l += 0.4 { - for r := int64(-2); r <= 2; r++ { - testBinaryOp(t, &objects.Float{Value: l}, token.Add, &objects.Int{Value: r}, &objects.Float{Value: l + float64(r)}) - } - } - - // float - int - for l := float64(-2); l <= 2.1; l += 0.4 { - for r := int64(-2); r <= 2; r++ { - testBinaryOp(t, &objects.Float{Value: l}, token.Sub, &objects.Int{Value: r}, &objects.Float{Value: l - float64(r)}) - } - } - - // float * int - for l := float64(-2); l <= 2.1; l += 0.4 { - for r := int64(-2); r <= 2; r++ { - testBinaryOp(t, &objects.Float{Value: l}, token.Mul, &objects.Int{Value: r}, &objects.Float{Value: l * float64(r)}) - } - } - - // float / int - for l := float64(-2); l <= 2.1; l += 0.4 { - for r := int64(-2); r <= 2; r++ { - if r != 0 { - testBinaryOp(t, &objects.Float{Value: l}, token.Quo, &objects.Int{Value: r}, &objects.Float{Value: l / float64(r)}) - } - } - } - - // float < int - for l := float64(-2); l <= 2.1; l += 0.4 { - for r := int64(-2); r <= 2; r++ { - testBinaryOp(t, &objects.Float{Value: l}, token.Less, &objects.Int{Value: r}, boolValue(l < float64(r))) - } - } - - // float > int - for l := float64(-2); l <= 2.1; l += 0.4 { - for r := int64(-2); r <= 2; r++ { - testBinaryOp(t, &objects.Float{Value: l}, token.Greater, &objects.Int{Value: r}, boolValue(l > float64(r))) - } - } - - // float <= int - for l := float64(-2); l <= 2.1; l += 0.4 { - for r := int64(-2); r <= 2; r++ { - testBinaryOp(t, &objects.Float{Value: l}, token.LessEq, &objects.Int{Value: r}, boolValue(l <= float64(r))) - } - } - - // float >= int - for l := float64(-2); l <= 2.1; l += 0.4 { - for r := int64(-2); r <= 2; r++ { - testBinaryOp(t, &objects.Float{Value: l}, token.GreaterEq, &objects.Int{Value: r}, boolValue(l >= float64(r))) - } - } -} diff --git a/objects/immutable_array.go b/objects/immutable_array.go deleted file mode 100644 index f3621e2..0000000 --- a/objects/immutable_array.go +++ /dev/null @@ -1,109 +0,0 @@ -package objects - -import ( - "fmt" - "strings" - - "github.com/d5/tengo/compiler/token" -) - -// ImmutableArray represents an immutable array of objects. -type ImmutableArray struct { - Value []Object -} - -// TypeName returns the name of the type. -func (o *ImmutableArray) TypeName() string { - return "immutable-array" -} - -func (o *ImmutableArray) String() string { - var elements []string - for _, e := range o.Value { - elements = append(elements, e.String()) - } - - return fmt.Sprintf("[%s]", strings.Join(elements, ", ")) -} - -// BinaryOp returns another object that is the result of -// a given binary operator and a right-hand side object. -func (o *ImmutableArray) BinaryOp(op token.Token, rhs Object) (Object, error) { - if rhs, ok := rhs.(*ImmutableArray); ok { - switch op { - case token.Add: - return &Array{Value: append(o.Value, rhs.Value...)}, nil - } - } - - return nil, ErrInvalidOperator -} - -// Copy returns a copy of the type. -func (o *ImmutableArray) Copy() Object { - var c []Object - for _, elem := range o.Value { - c = append(c, elem.Copy()) - } - - return &Array{Value: c} -} - -// IsFalsy returns true if the value of the type is falsy. -func (o *ImmutableArray) IsFalsy() bool { - return len(o.Value) == 0 -} - -// Equals returns true if the value of the type -// is equal to the value of another object. -func (o *ImmutableArray) Equals(x Object) bool { - var xVal []Object - switch x := x.(type) { - case *Array: - xVal = x.Value - case *ImmutableArray: - xVal = x.Value - default: - return false - } - - if len(o.Value) != len(xVal) { - return false - } - - for i, e := range o.Value { - if !e.Equals(xVal[i]) { - return false - } - } - - return true -} - -// IndexGet returns an element at a given index. -func (o *ImmutableArray) IndexGet(index Object) (res Object, err error) { - intIdx, ok := index.(*Int) - if !ok { - err = ErrInvalidIndexType - return - } - - idxVal := int(intIdx.Value) - - if idxVal < 0 || idxVal >= len(o.Value) { - res = UndefinedValue - return - } - - res = o.Value[idxVal] - - return -} - -// Iterate creates an array iterator. -func (o *ImmutableArray) Iterate() Iterator { - return &ArrayIterator{ - v: o.Value, - l: len(o.Value), - } -} diff --git a/objects/immutable_map.go b/objects/immutable_map.go deleted file mode 100644 index 8f58701..0000000 --- a/objects/immutable_map.go +++ /dev/null @@ -1,105 +0,0 @@ -package objects - -import ( - "fmt" - "strings" - - "github.com/d5/tengo/compiler/token" -) - -// ImmutableMap represents an immutable map object. -type ImmutableMap struct { - Value map[string]Object -} - -// TypeName returns the name of the type. -func (o *ImmutableMap) TypeName() string { - return "immutable-map" -} - -func (o *ImmutableMap) String() string { - var pairs []string - for k, v := range o.Value { - pairs = append(pairs, fmt.Sprintf("%s: %s", k, v.String())) - } - - return fmt.Sprintf("{%s}", strings.Join(pairs, ", ")) -} - -// BinaryOp returns another object that is the result of -// a given binary operator and a right-hand side object. -func (o *ImmutableMap) BinaryOp(op token.Token, rhs Object) (Object, error) { - return nil, ErrInvalidOperator -} - -// Copy returns a copy of the type. -func (o *ImmutableMap) Copy() Object { - c := make(map[string]Object) - for k, v := range o.Value { - c[k] = v.Copy() - } - - return &Map{Value: c} -} - -// IsFalsy returns true if the value of the type is falsy. -func (o *ImmutableMap) IsFalsy() bool { - return len(o.Value) == 0 -} - -// IndexGet returns the value for the given key. -func (o *ImmutableMap) IndexGet(index Object) (res Object, err error) { - strIdx, ok := ToString(index) - if !ok { - err = ErrInvalidIndexType - return - } - - val, ok := o.Value[strIdx] - if !ok { - val = UndefinedValue - } - - return val, nil -} - -// Equals returns true if the value of the type -// is equal to the value of another object. -func (o *ImmutableMap) Equals(x Object) bool { - var xVal map[string]Object - switch x := x.(type) { - case *Map: - xVal = x.Value - case *ImmutableMap: - xVal = x.Value - default: - return false - } - - if len(o.Value) != len(xVal) { - return false - } - - for k, v := range o.Value { - tv := xVal[k] - if !v.Equals(tv) { - return false - } - } - - return true -} - -// Iterate creates an immutable map iterator. -func (o *ImmutableMap) Iterate() Iterator { - var keys []string - for k := range o.Value { - keys = append(keys, k) - } - - return &MapIterator{ - v: o.Value, - k: keys, - l: len(keys), - } -} diff --git a/objects/importable.go b/objects/importable.go deleted file mode 100644 index 9fd86ae..0000000 --- a/objects/importable.go +++ /dev/null @@ -1,7 +0,0 @@ -package objects - -// Importable interface represents importable module instance. -type Importable interface { - // Import should return either an Object or module source code ([]byte). - Import(moduleName string) (interface{}, error) -} diff --git a/objects/index_assignable.go b/objects/index_assignable.go deleted file mode 100644 index a1c6cbf..0000000 --- a/objects/index_assignable.go +++ /dev/null @@ -1,9 +0,0 @@ -package objects - -// IndexAssignable is an object that can take an index and a value -// on the left-hand side of the assignment statement. -type IndexAssignable interface { - // IndexSet should take an index Object and a value Object. - // If an error is returned, it will be treated as a run-time error. - IndexSet(index, value Object) error -} diff --git a/objects/indexable.go b/objects/indexable.go deleted file mode 100644 index bbc8163..0000000 --- a/objects/indexable.go +++ /dev/null @@ -1,9 +0,0 @@ -package objects - -// Indexable is an object that can take an index and return an object. -type Indexable interface { - // IndexGet should take an index Object and return a result Object or an error. - // If error is returned, the runtime will treat it as a run-time error and ignore returned value. - // If nil is returned as value, it will be converted to Undefined value by the runtime. - IndexGet(index Object) (value Object, err error) -} diff --git a/objects/int.go b/objects/int.go deleted file mode 100644 index e902c93..0000000 --- a/objects/int.go +++ /dev/null @@ -1,198 +0,0 @@ -package objects - -import ( - "strconv" - - "github.com/d5/tengo/compiler/token" -) - -// Int represents an integer value. -type Int struct { - Value int64 -} - -func (o *Int) String() string { - return strconv.FormatInt(o.Value, 10) -} - -// TypeName returns the name of the type. -func (o *Int) TypeName() string { - return "int" -} - -// BinaryOp returns another object that is the result of -// a given binary operator and a right-hand side object. -func (o *Int) BinaryOp(op token.Token, rhs Object) (Object, error) { - switch rhs := rhs.(type) { - case *Int: - switch op { - case token.Add: - r := o.Value + rhs.Value - if r == o.Value { - return o, nil - } - return &Int{Value: r}, nil - case token.Sub: - r := o.Value - rhs.Value - if r == o.Value { - return o, nil - } - return &Int{Value: r}, nil - case token.Mul: - r := o.Value * rhs.Value - if r == o.Value { - return o, nil - } - return &Int{Value: r}, nil - case token.Quo: - r := o.Value / rhs.Value - if r == o.Value { - return o, nil - } - return &Int{Value: r}, nil - case token.Rem: - r := o.Value % rhs.Value - if r == o.Value { - return o, nil - } - return &Int{Value: r}, nil - case token.And: - r := o.Value & rhs.Value - if r == o.Value { - return o, nil - } - return &Int{Value: r}, nil - case token.Or: - r := o.Value | rhs.Value - if r == o.Value { - return o, nil - } - return &Int{Value: r}, nil - case token.Xor: - r := o.Value ^ rhs.Value - if r == o.Value { - return o, nil - } - return &Int{Value: r}, nil - case token.AndNot: - r := o.Value &^ rhs.Value - if r == o.Value { - return o, nil - } - return &Int{Value: r}, nil - case token.Shl: - r := o.Value << uint64(rhs.Value) - if r == o.Value { - return o, nil - } - return &Int{Value: r}, nil - case token.Shr: - r := o.Value >> uint64(rhs.Value) - if r == o.Value { - return o, nil - } - return &Int{Value: r}, nil - case token.Less: - if o.Value < rhs.Value { - return TrueValue, nil - } - return FalseValue, nil - case token.Greater: - if o.Value > rhs.Value { - return TrueValue, nil - } - return FalseValue, nil - case token.LessEq: - if o.Value <= rhs.Value { - return TrueValue, nil - } - return FalseValue, nil - case token.GreaterEq: - if o.Value >= rhs.Value { - return TrueValue, nil - } - return FalseValue, nil - } - case *Float: - switch op { - case token.Add: - return &Float{float64(o.Value) + rhs.Value}, nil - case token.Sub: - return &Float{float64(o.Value) - rhs.Value}, nil - case token.Mul: - return &Float{float64(o.Value) * rhs.Value}, nil - case token.Quo: - return &Float{float64(o.Value) / rhs.Value}, nil - case token.Less: - if float64(o.Value) < rhs.Value { - return TrueValue, nil - } - return FalseValue, nil - case token.Greater: - if float64(o.Value) > rhs.Value { - return TrueValue, nil - } - return FalseValue, nil - case token.LessEq: - if float64(o.Value) <= rhs.Value { - return TrueValue, nil - } - return FalseValue, nil - case token.GreaterEq: - if float64(o.Value) >= rhs.Value { - return TrueValue, nil - } - return FalseValue, nil - } - case *Char: - switch op { - case token.Add: - return &Char{rune(o.Value) + rhs.Value}, nil - case token.Sub: - return &Char{rune(o.Value) - rhs.Value}, nil - case token.Less: - if o.Value < int64(rhs.Value) { - return TrueValue, nil - } - return FalseValue, nil - case token.Greater: - if o.Value > int64(rhs.Value) { - return TrueValue, nil - } - return FalseValue, nil - case token.LessEq: - if o.Value <= int64(rhs.Value) { - return TrueValue, nil - } - return FalseValue, nil - case token.GreaterEq: - if o.Value >= int64(rhs.Value) { - return TrueValue, nil - } - return FalseValue, nil - } - } - - return nil, ErrInvalidOperator -} - -// Copy returns a copy of the type. -func (o *Int) Copy() Object { - return &Int{Value: o.Value} -} - -// IsFalsy returns true if the value of the type is falsy. -func (o *Int) IsFalsy() bool { - return o.Value == 0 -} - -// Equals returns true if the value of the type -// is equal to the value of another object. -func (o *Int) Equals(x Object) bool { - t, ok := x.(*Int) - if !ok { - return false - } - - return o.Value == t.Value -} diff --git a/objects/int_test.go b/objects/int_test.go deleted file mode 100644 index 8e7b350..0000000 --- a/objects/int_test.go +++ /dev/null @@ -1,199 +0,0 @@ -package objects_test - -import ( - "testing" - - "github.com/d5/tengo/compiler/token" - "github.com/d5/tengo/objects" -) - -func TestInt_BinaryOp(t *testing.T) { - // int + int - for l := int64(-2); l <= 2; l++ { - for r := int64(-2); r <= 2; r++ { - testBinaryOp(t, &objects.Int{Value: l}, token.Add, &objects.Int{Value: r}, &objects.Int{Value: l + r}) - } - } - - // int - int - for l := int64(-2); l <= 2; l++ { - for r := int64(-2); r <= 2; r++ { - testBinaryOp(t, &objects.Int{Value: l}, token.Sub, &objects.Int{Value: r}, &objects.Int{Value: l - r}) - } - } - - // int * int - for l := int64(-2); l <= 2; l++ { - for r := int64(-2); r <= 2; r++ { - testBinaryOp(t, &objects.Int{Value: l}, token.Mul, &objects.Int{Value: r}, &objects.Int{Value: l * r}) - } - } - - // int / int - for l := int64(-2); l <= 2; l++ { - for r := int64(-2); r <= 2; r++ { - if r != 0 { - testBinaryOp(t, &objects.Int{Value: l}, token.Quo, &objects.Int{Value: r}, &objects.Int{Value: l / r}) - } - } - } - - // int % int - for l := int64(-4); l <= 4; l++ { - for r := -int64(-4); r <= 4; r++ { - if r == 0 { - testBinaryOp(t, &objects.Int{Value: l}, token.Rem, &objects.Int{Value: r}, &objects.Int{Value: l % r}) - } - } - } - - // int & int - testBinaryOp(t, &objects.Int{Value: 0}, token.And, &objects.Int{Value: 0}, &objects.Int{Value: int64(0) & int64(0)}) - testBinaryOp(t, &objects.Int{Value: 1}, token.And, &objects.Int{Value: 0}, &objects.Int{Value: int64(1) & int64(0)}) - testBinaryOp(t, &objects.Int{Value: 0}, token.And, &objects.Int{Value: 1}, &objects.Int{Value: int64(0) & int64(1)}) - testBinaryOp(t, &objects.Int{Value: 1}, token.And, &objects.Int{Value: 1}, &objects.Int{Value: int64(1) & int64(1)}) - testBinaryOp(t, &objects.Int{Value: 0}, token.And, &objects.Int{Value: int64(0xffffffff)}, &objects.Int{Value: int64(0) & int64(0xffffffff)}) - testBinaryOp(t, &objects.Int{Value: 1}, token.And, &objects.Int{Value: int64(0xffffffff)}, &objects.Int{Value: int64(1) & int64(0xffffffff)}) - testBinaryOp(t, &objects.Int{Value: int64(0xffffffff)}, token.And, &objects.Int{Value: int64(0xffffffff)}, &objects.Int{Value: int64(0xffffffff) & int64(0xffffffff)}) - testBinaryOp(t, &objects.Int{Value: 1984}, token.And, &objects.Int{Value: int64(0xffffffff)}, &objects.Int{Value: int64(1984) & int64(0xffffffff)}) - testBinaryOp(t, &objects.Int{Value: -1984}, token.And, &objects.Int{Value: int64(0xffffffff)}, &objects.Int{Value: int64(-1984) & int64(0xffffffff)}) - - // int | int - testBinaryOp(t, &objects.Int{Value: 0}, token.Or, &objects.Int{Value: 0}, &objects.Int{Value: int64(0) | int64(0)}) - testBinaryOp(t, &objects.Int{Value: 1}, token.Or, &objects.Int{Value: 0}, &objects.Int{Value: int64(1) | int64(0)}) - testBinaryOp(t, &objects.Int{Value: 0}, token.Or, &objects.Int{Value: 1}, &objects.Int{Value: int64(0) | int64(1)}) - testBinaryOp(t, &objects.Int{Value: 1}, token.Or, &objects.Int{Value: 1}, &objects.Int{Value: int64(1) | int64(1)}) - testBinaryOp(t, &objects.Int{Value: 0}, token.Or, &objects.Int{Value: int64(0xffffffff)}, &objects.Int{Value: int64(0) | int64(0xffffffff)}) - testBinaryOp(t, &objects.Int{Value: 1}, token.Or, &objects.Int{Value: int64(0xffffffff)}, &objects.Int{Value: int64(1) | int64(0xffffffff)}) - testBinaryOp(t, &objects.Int{Value: int64(0xffffffff)}, token.Or, &objects.Int{Value: int64(0xffffffff)}, &objects.Int{Value: int64(0xffffffff) | int64(0xffffffff)}) - testBinaryOp(t, &objects.Int{Value: 1984}, token.Or, &objects.Int{Value: int64(0xffffffff)}, &objects.Int{Value: int64(1984) | int64(0xffffffff)}) - testBinaryOp(t, &objects.Int{Value: -1984}, token.Or, &objects.Int{Value: int64(0xffffffff)}, &objects.Int{Value: int64(-1984) | int64(0xffffffff)}) - - // int ^ int - testBinaryOp(t, &objects.Int{Value: 0}, token.Xor, &objects.Int{Value: 0}, &objects.Int{Value: int64(0) ^ int64(0)}) - testBinaryOp(t, &objects.Int{Value: 1}, token.Xor, &objects.Int{Value: 0}, &objects.Int{Value: int64(1) ^ int64(0)}) - testBinaryOp(t, &objects.Int{Value: 0}, token.Xor, &objects.Int{Value: 1}, &objects.Int{Value: int64(0) ^ int64(1)}) - testBinaryOp(t, &objects.Int{Value: 1}, token.Xor, &objects.Int{Value: 1}, &objects.Int{Value: int64(1) ^ int64(1)}) - testBinaryOp(t, &objects.Int{Value: 0}, token.Xor, &objects.Int{Value: int64(0xffffffff)}, &objects.Int{Value: int64(0) ^ int64(0xffffffff)}) - testBinaryOp(t, &objects.Int{Value: 1}, token.Xor, &objects.Int{Value: int64(0xffffffff)}, &objects.Int{Value: int64(1) ^ int64(0xffffffff)}) - testBinaryOp(t, &objects.Int{Value: int64(0xffffffff)}, token.Xor, &objects.Int{Value: int64(0xffffffff)}, &objects.Int{Value: int64(0xffffffff) ^ int64(0xffffffff)}) - testBinaryOp(t, &objects.Int{Value: 1984}, token.Xor, &objects.Int{Value: int64(0xffffffff)}, &objects.Int{Value: int64(1984) ^ int64(0xffffffff)}) - testBinaryOp(t, &objects.Int{Value: -1984}, token.Xor, &objects.Int{Value: int64(0xffffffff)}, &objects.Int{Value: int64(-1984) ^ int64(0xffffffff)}) - - // int &^ int - testBinaryOp(t, &objects.Int{Value: 0}, token.AndNot, &objects.Int{Value: 0}, &objects.Int{Value: int64(0) &^ int64(0)}) - testBinaryOp(t, &objects.Int{Value: 1}, token.AndNot, &objects.Int{Value: 0}, &objects.Int{Value: int64(1) &^ int64(0)}) - testBinaryOp(t, &objects.Int{Value: 0}, token.AndNot, &objects.Int{Value: 1}, &objects.Int{Value: int64(0) &^ int64(1)}) - testBinaryOp(t, &objects.Int{Value: 1}, token.AndNot, &objects.Int{Value: 1}, &objects.Int{Value: int64(1) &^ int64(1)}) - testBinaryOp(t, &objects.Int{Value: 0}, token.AndNot, &objects.Int{Value: int64(0xffffffff)}, &objects.Int{Value: int64(0) &^ int64(0xffffffff)}) - testBinaryOp(t, &objects.Int{Value: 1}, token.AndNot, &objects.Int{Value: int64(0xffffffff)}, &objects.Int{Value: int64(1) &^ int64(0xffffffff)}) - testBinaryOp(t, &objects.Int{Value: int64(0xffffffff)}, token.AndNot, &objects.Int{Value: int64(0xffffffff)}, &objects.Int{Value: int64(0xffffffff) &^ int64(0xffffffff)}) - testBinaryOp(t, &objects.Int{Value: 1984}, token.AndNot, &objects.Int{Value: int64(0xffffffff)}, &objects.Int{Value: int64(1984) &^ int64(0xffffffff)}) - testBinaryOp(t, &objects.Int{Value: -1984}, token.AndNot, &objects.Int{Value: int64(0xffffffff)}, &objects.Int{Value: int64(-1984) &^ int64(0xffffffff)}) - - // int << int - for s := int64(0); s < 64; s++ { - testBinaryOp(t, &objects.Int{Value: 0}, token.Shl, &objects.Int{Value: s}, &objects.Int{Value: int64(0) << uint(s)}) - testBinaryOp(t, &objects.Int{Value: 1}, token.Shl, &objects.Int{Value: s}, &objects.Int{Value: int64(1) << uint(s)}) - testBinaryOp(t, &objects.Int{Value: 2}, token.Shl, &objects.Int{Value: s}, &objects.Int{Value: int64(2) << uint(s)}) - testBinaryOp(t, &objects.Int{Value: -1}, token.Shl, &objects.Int{Value: s}, &objects.Int{Value: int64(-1) << uint(s)}) - testBinaryOp(t, &objects.Int{Value: -2}, token.Shl, &objects.Int{Value: s}, &objects.Int{Value: int64(-2) << uint(s)}) - testBinaryOp(t, &objects.Int{Value: int64(0xffffffff)}, token.Shl, &objects.Int{Value: s}, &objects.Int{Value: int64(0xffffffff) << uint(s)}) - } - - // int >> int - for s := int64(0); s < 64; s++ { - testBinaryOp(t, &objects.Int{Value: 0}, token.Shr, &objects.Int{Value: s}, &objects.Int{Value: int64(0) >> uint(s)}) - testBinaryOp(t, &objects.Int{Value: 1}, token.Shr, &objects.Int{Value: s}, &objects.Int{Value: int64(1) >> uint(s)}) - testBinaryOp(t, &objects.Int{Value: 2}, token.Shr, &objects.Int{Value: s}, &objects.Int{Value: int64(2) >> uint(s)}) - testBinaryOp(t, &objects.Int{Value: -1}, token.Shr, &objects.Int{Value: s}, &objects.Int{Value: int64(-1) >> uint(s)}) - testBinaryOp(t, &objects.Int{Value: -2}, token.Shr, &objects.Int{Value: s}, &objects.Int{Value: int64(-2) >> uint(s)}) - testBinaryOp(t, &objects.Int{Value: int64(0xffffffff)}, token.Shr, &objects.Int{Value: s}, &objects.Int{Value: int64(0xffffffff) >> uint(s)}) - } - - // int < int - for l := int64(-2); l <= 2; l++ { - for r := int64(-2); r <= 2; r++ { - testBinaryOp(t, &objects.Int{Value: l}, token.Less, &objects.Int{Value: r}, boolValue(l < r)) - } - } - - // int > int - for l := int64(-2); l <= 2; l++ { - for r := int64(-2); r <= 2; r++ { - testBinaryOp(t, &objects.Int{Value: l}, token.Greater, &objects.Int{Value: r}, boolValue(l > r)) - } - } - - // int <= int - for l := int64(-2); l <= 2; l++ { - for r := int64(-2); r <= 2; r++ { - testBinaryOp(t, &objects.Int{Value: l}, token.LessEq, &objects.Int{Value: r}, boolValue(l <= r)) - } - } - - // int >= int - for l := int64(-2); l <= 2; l++ { - for r := int64(-2); r <= 2; r++ { - testBinaryOp(t, &objects.Int{Value: l}, token.GreaterEq, &objects.Int{Value: r}, boolValue(l >= r)) - } - } - - // int + float - for l := int64(-2); l <= 2; l++ { - for r := float64(-2); r <= 2.1; r += 0.5 { - testBinaryOp(t, &objects.Int{Value: l}, token.Add, &objects.Float{Value: r}, &objects.Float{Value: float64(l) + r}) - } - } - - // int - float - for l := int64(-2); l <= 2; l++ { - for r := float64(-2); r <= 2.1; r += 0.5 { - testBinaryOp(t, &objects.Int{Value: l}, token.Sub, &objects.Float{Value: r}, &objects.Float{Value: float64(l) - r}) - } - } - - // int * float - for l := int64(-2); l <= 2; l++ { - for r := float64(-2); r <= 2.1; r += 0.5 { - testBinaryOp(t, &objects.Int{Value: l}, token.Mul, &objects.Float{Value: r}, &objects.Float{Value: float64(l) * r}) - } - } - - // int / float - for l := int64(-2); l <= 2; l++ { - for r := float64(-2); r <= 2.1; r += 0.5 { - if r != 0 { - testBinaryOp(t, &objects.Int{Value: l}, token.Quo, &objects.Float{Value: r}, &objects.Float{Value: float64(l) / r}) - } - } - } - - // int < float - for l := int64(-2); l <= 2; l++ { - for r := float64(-2); r <= 2.1; r += 0.5 { - testBinaryOp(t, &objects.Int{Value: l}, token.Less, &objects.Float{Value: r}, boolValue(float64(l) < r)) - } - } - - // int > float - for l := int64(-2); l <= 2; l++ { - for r := float64(-2); r <= 2.1; r += 0.5 { - testBinaryOp(t, &objects.Int{Value: l}, token.Greater, &objects.Float{Value: r}, boolValue(float64(l) > r)) - } - } - - // int <= float - for l := int64(-2); l <= 2; l++ { - for r := float64(-2); r <= 2.1; r += 0.5 { - testBinaryOp(t, &objects.Int{Value: l}, token.LessEq, &objects.Float{Value: r}, boolValue(float64(l) <= r)) - } - } - - // int >= float - for l := int64(-2); l <= 2; l++ { - for r := float64(-2); r <= 2.1; r += 0.5 { - testBinaryOp(t, &objects.Int{Value: l}, token.GreaterEq, &objects.Float{Value: r}, boolValue(float64(l) >= r)) - } - } -} diff --git a/objects/iterable.go b/objects/iterable.go deleted file mode 100644 index e431d3d..0000000 --- a/objects/iterable.go +++ /dev/null @@ -1,7 +0,0 @@ -package objects - -// Iterable represents an object that has iterator. -type Iterable interface { - // Iterate should return an Iterator for the type. - Iterate() Iterator -} diff --git a/objects/iterator.go b/objects/iterator.go deleted file mode 100644 index 01522ba..0000000 --- a/objects/iterator.go +++ /dev/null @@ -1,15 +0,0 @@ -package objects - -// Iterator represents an iterator for underlying data type. -type Iterator interface { - Object - - // Next returns true if there are more elements to iterate. - Next() bool - - // Key returns the key or index value of the current element. - Key() Object - - // Value returns the value of the current element. - Value() Object -} diff --git a/objects/map.go b/objects/map.go deleted file mode 100644 index 9208872..0000000 --- a/objects/map.go +++ /dev/null @@ -1,118 +0,0 @@ -package objects - -import ( - "fmt" - "strings" - - "github.com/d5/tengo/compiler/token" -) - -// Map represents a map of objects. -type Map struct { - Value map[string]Object -} - -// TypeName returns the name of the type. -func (o *Map) TypeName() string { - return "map" -} - -func (o *Map) String() string { - var pairs []string - for k, v := range o.Value { - pairs = append(pairs, fmt.Sprintf("%s: %s", k, v.String())) - } - - return fmt.Sprintf("{%s}", strings.Join(pairs, ", ")) -} - -// BinaryOp returns another object that is the result of -// a given binary operator and a right-hand side object. -func (o *Map) BinaryOp(op token.Token, rhs Object) (Object, error) { - return nil, ErrInvalidOperator -} - -// Copy returns a copy of the type. -func (o *Map) Copy() Object { - c := make(map[string]Object) - for k, v := range o.Value { - c[k] = v.Copy() - } - - return &Map{Value: c} -} - -// IsFalsy returns true if the value of the type is falsy. -func (o *Map) IsFalsy() bool { - return len(o.Value) == 0 -} - -// Equals returns true if the value of the type -// is equal to the value of another object. -func (o *Map) Equals(x Object) bool { - var xVal map[string]Object - switch x := x.(type) { - case *Map: - xVal = x.Value - case *ImmutableMap: - xVal = x.Value - default: - return false - } - - if len(o.Value) != len(xVal) { - return false - } - - for k, v := range o.Value { - tv := xVal[k] - if !v.Equals(tv) { - return false - } - } - - return true -} - -// IndexGet returns the value for the given key. -func (o *Map) IndexGet(index Object) (res Object, err error) { - strIdx, ok := ToString(index) - if !ok { - err = ErrInvalidIndexType - return - } - - val, ok := o.Value[strIdx] - if !ok { - val = UndefinedValue - } - - return val, nil -} - -// IndexSet sets the value for the given key. -func (o *Map) IndexSet(index, value Object) (err error) { - strIdx, ok := ToString(index) - if !ok { - err = ErrInvalidIndexType - return - } - - o.Value[strIdx] = value - - return nil -} - -// Iterate creates a map iterator. -func (o *Map) Iterate() Iterator { - var keys []string - for k := range o.Value { - keys = append(keys, k) - } - - return &MapIterator{ - v: o.Value, - k: keys, - l: len(keys), - } -} diff --git a/objects/map_iterator.go b/objects/map_iterator.go deleted file mode 100644 index d60dd0e..0000000 --- a/objects/map_iterator.go +++ /dev/null @@ -1,62 +0,0 @@ -package objects - -import "github.com/d5/tengo/compiler/token" - -// MapIterator represents an iterator for the map. -type MapIterator struct { - v map[string]Object - k []string - i int - l int -} - -// TypeName returns the name of the type. -func (i *MapIterator) TypeName() string { - return "map-iterator" -} - -func (i *MapIterator) String() string { - return "" -} - -// BinaryOp returns another object that is the result of -// a given binary operator and a right-hand side object. -func (i *MapIterator) BinaryOp(op token.Token, rhs Object) (Object, error) { - return nil, ErrInvalidOperator -} - -// IsFalsy returns true if the value of the type is falsy. -func (i *MapIterator) IsFalsy() bool { - return true -} - -// Equals returns true if the value of the type -// is equal to the value of another object. -func (i *MapIterator) Equals(Object) bool { - return false -} - -// Copy returns a copy of the type. -func (i *MapIterator) Copy() Object { - return &MapIterator{v: i.v, k: i.k, i: i.i, l: i.l} -} - -// Next returns true if there are more elements to iterate. -func (i *MapIterator) Next() bool { - i.i++ - return i.i <= i.l -} - -// Key returns the key or index value of the current element. -func (i *MapIterator) Key() Object { - k := i.k[i.i-1] - - return &String{Value: k} -} - -// Value returns the value of the current element. -func (i *MapIterator) Value() Object { - k := i.k[i.i-1] - - return i.v[k] -} diff --git a/objects/map_test.go b/objects/map_test.go deleted file mode 100644 index 8dafa56..0000000 --- a/objects/map_test.go +++ /dev/null @@ -1,21 +0,0 @@ -package objects_test - -import ( - "testing" - - "github.com/d5/tengo/assert" - "github.com/d5/tengo/objects" -) - -func TestMap_Index(t *testing.T) { - m := &objects.Map{Value: make(map[string]objects.Object)} - k := &objects.Int{Value: 1} - v := &objects.String{Value: "abcdef"} - err := m.IndexSet(k, v) - - assert.NoError(t, err) - - res, err := m.IndexGet(k) - assert.NoError(t, err) - assert.Equal(t, v, res) -} diff --git a/objects/object.go b/objects/object.go deleted file mode 100644 index 4c5aa7a..0000000 --- a/objects/object.go +++ /dev/null @@ -1,30 +0,0 @@ -package objects - -import "github.com/d5/tengo/compiler/token" - -// Object represents an object in the VM. -type Object interface { - // TypeName should return the name of the type. - TypeName() string - - // String should return a string representation of the type's value. - String() string - - // BinaryOp should return another object that is the result of - // a given binary operator and a right-hand side object. - // If BinaryOp returns an error, the VM will treat it as a run-time error. - BinaryOp(op token.Token, rhs Object) (Object, error) - - // IsFalsy should return true if the value of the type - // should be considered as falsy. - IsFalsy() bool - - // Equals should return true if the value of the type - // should be considered as equal to the value of another object. - Equals(another Object) bool - - // Copy should return a copy of the type (and its value). - // Copy function will be used for copy() builtin function - // which is expected to deep-copy the values generally. - Copy() Object -} diff --git a/objects/object_ptr.go b/objects/object_ptr.go deleted file mode 100644 index 2c87c56..0000000 --- a/objects/object_ptr.go +++ /dev/null @@ -1,41 +0,0 @@ -package objects - -import ( - "github.com/d5/tengo/compiler/token" -) - -// ObjectPtr represents a free variable. -type ObjectPtr struct { - Value *Object -} - -func (o *ObjectPtr) String() string { - return "free-var" -} - -// TypeName returns the name of the type. -func (o *ObjectPtr) TypeName() string { - return "" -} - -// BinaryOp returns another object that is the result of -// a given binary operator and a right-hand side object. -func (o *ObjectPtr) BinaryOp(op token.Token, rhs Object) (Object, error) { - return nil, ErrInvalidOperator -} - -// Copy returns a copy of the type. -func (o *ObjectPtr) Copy() Object { - return o -} - -// IsFalsy returns true if the value of the type is falsy. -func (o *ObjectPtr) IsFalsy() bool { - return o.Value == nil -} - -// Equals returns true if the value of the type -// is equal to the value of another object. -func (o *ObjectPtr) Equals(x Object) bool { - return o == x -} diff --git a/objects/object_test.go b/objects/object_test.go deleted file mode 100644 index 31147ec..0000000 --- a/objects/object_test.go +++ /dev/null @@ -1,168 +0,0 @@ -package objects_test - -import ( - "testing" - - "github.com/d5/tengo/assert" - "github.com/d5/tengo/compiler/token" - "github.com/d5/tengo/objects" -) - -func TestObject_TypeName(t *testing.T) { - var o objects.Object = &objects.Int{} - assert.Equal(t, "int", o.TypeName()) - o = &objects.Float{} - assert.Equal(t, "float", o.TypeName()) - o = &objects.Char{} - assert.Equal(t, "char", o.TypeName()) - o = &objects.String{} - assert.Equal(t, "string", o.TypeName()) - o = &objects.Bool{} - assert.Equal(t, "bool", o.TypeName()) - o = &objects.Array{} - assert.Equal(t, "array", o.TypeName()) - o = &objects.Map{} - assert.Equal(t, "map", o.TypeName()) - o = &objects.ArrayIterator{} - assert.Equal(t, "array-iterator", o.TypeName()) - o = &objects.StringIterator{} - assert.Equal(t, "string-iterator", o.TypeName()) - o = &objects.MapIterator{} - assert.Equal(t, "map-iterator", o.TypeName()) - o = &objects.BuiltinFunction{Name: "fn"} - assert.Equal(t, "builtin-function:fn", o.TypeName()) - o = &objects.UserFunction{Name: "fn"} - assert.Equal(t, "user-function:fn", o.TypeName()) - o = &objects.Closure{} - assert.Equal(t, "closure", o.TypeName()) - o = &objects.CompiledFunction{} - assert.Equal(t, "compiled-function", o.TypeName()) - o = &objects.Undefined{} - assert.Equal(t, "undefined", o.TypeName()) - o = &objects.Error{} - assert.Equal(t, "error", o.TypeName()) - o = &objects.Bytes{} - assert.Equal(t, "bytes", o.TypeName()) -} - -func TestObject_IsFalsy(t *testing.T) { - var o objects.Object = &objects.Int{Value: 0} - assert.True(t, o.IsFalsy()) - o = &objects.Int{Value: 1} - assert.False(t, o.IsFalsy()) - o = &objects.Float{Value: 0} - assert.False(t, o.IsFalsy()) - o = &objects.Float{Value: 1} - assert.False(t, o.IsFalsy()) - o = &objects.Char{Value: ' '} - assert.False(t, o.IsFalsy()) - o = &objects.Char{Value: 'T'} - assert.False(t, o.IsFalsy()) - o = &objects.String{Value: ""} - assert.True(t, o.IsFalsy()) - o = &objects.String{Value: " "} - assert.False(t, o.IsFalsy()) - o = &objects.Array{Value: nil} - assert.True(t, o.IsFalsy()) - o = &objects.Array{Value: []objects.Object{nil}} // nil is not valid but still count as 1 element - assert.False(t, o.IsFalsy()) - o = &objects.Map{Value: nil} - assert.True(t, o.IsFalsy()) - o = &objects.Map{Value: map[string]objects.Object{"a": nil}} // nil is not valid but still count as 1 element - assert.False(t, o.IsFalsy()) - o = &objects.StringIterator{} - assert.True(t, o.IsFalsy()) - o = &objects.ArrayIterator{} - assert.True(t, o.IsFalsy()) - o = &objects.MapIterator{} - assert.True(t, o.IsFalsy()) - o = &objects.BuiltinFunction{} - assert.False(t, o.IsFalsy()) - o = &objects.Closure{} - assert.False(t, o.IsFalsy()) - o = &objects.CompiledFunction{} - assert.False(t, o.IsFalsy()) - o = &objects.Undefined{} - assert.True(t, o.IsFalsy()) - o = &objects.Error{} - assert.True(t, o.IsFalsy()) - o = &objects.Bytes{} - assert.True(t, o.IsFalsy()) - o = &objects.Bytes{Value: []byte{1, 2}} - assert.False(t, o.IsFalsy()) -} - -func TestObject_String(t *testing.T) { - var o objects.Object = &objects.Int{Value: 0} - assert.Equal(t, "0", o.String()) - o = &objects.Int{Value: 1} - assert.Equal(t, "1", o.String()) - o = &objects.Float{Value: 0} - assert.Equal(t, "0", o.String()) - o = &objects.Float{Value: 1} - assert.Equal(t, "1", o.String()) - o = &objects.Char{Value: ' '} - assert.Equal(t, " ", o.String()) - o = &objects.Char{Value: 'T'} - assert.Equal(t, "T", o.String()) - o = &objects.String{Value: ""} - assert.Equal(t, `""`, o.String()) - o = &objects.String{Value: " "} - assert.Equal(t, `" "`, o.String()) - o = &objects.Array{Value: nil} - assert.Equal(t, "[]", o.String()) - o = &objects.Map{Value: nil} - assert.Equal(t, "{}", o.String()) - o = &objects.Error{Value: nil} - assert.Equal(t, "error", o.String()) - o = &objects.Error{Value: &objects.String{Value: "error 1"}} - assert.Equal(t, `error: "error 1"`, o.String()) - o = &objects.StringIterator{} - assert.Equal(t, "", o.String()) - o = &objects.ArrayIterator{} - assert.Equal(t, "", o.String()) - o = &objects.MapIterator{} - assert.Equal(t, "", o.String()) - o = &objects.Undefined{} - assert.Equal(t, "", o.String()) - o = &objects.Bytes{} - assert.Equal(t, "", o.String()) - o = &objects.Bytes{Value: []byte("foo")} - assert.Equal(t, "foo", o.String()) -} - -func TestObject_BinaryOp(t *testing.T) { - var o objects.Object = &objects.Char{} - _, err := o.BinaryOp(token.Add, objects.UndefinedValue) - assert.Error(t, err) - o = &objects.Bool{} - _, err = o.BinaryOp(token.Add, objects.UndefinedValue) - assert.Error(t, err) - o = &objects.Map{} - _, err = o.BinaryOp(token.Add, objects.UndefinedValue) - assert.Error(t, err) - o = &objects.ArrayIterator{} - _, err = o.BinaryOp(token.Add, objects.UndefinedValue) - assert.Error(t, err) - o = &objects.StringIterator{} - _, err = o.BinaryOp(token.Add, objects.UndefinedValue) - assert.Error(t, err) - o = &objects.MapIterator{} - _, err = o.BinaryOp(token.Add, objects.UndefinedValue) - assert.Error(t, err) - o = &objects.BuiltinFunction{} - _, err = o.BinaryOp(token.Add, objects.UndefinedValue) - assert.Error(t, err) - o = &objects.Closure{} - _, err = o.BinaryOp(token.Add, objects.UndefinedValue) - assert.Error(t, err) - o = &objects.CompiledFunction{} - _, err = o.BinaryOp(token.Add, objects.UndefinedValue) - assert.Error(t, err) - o = &objects.Undefined{} - _, err = o.BinaryOp(token.Add, objects.UndefinedValue) - assert.Error(t, err) - o = &objects.Error{} - _, err = o.BinaryOp(token.Add, objects.UndefinedValue) - assert.Error(t, err) -} diff --git a/objects/objects.go b/objects/objects.go deleted file mode 100644 index f3878b1..0000000 --- a/objects/objects.go +++ /dev/null @@ -1,12 +0,0 @@ -package objects - -var ( - // TrueValue represents a true value. - TrueValue Object = &Bool{value: true} - - // FalseValue represents a false value. - FalseValue Object = &Bool{value: false} - - // UndefinedValue represents an undefined value. - UndefinedValue Object = &Undefined{} -) diff --git a/objects/objects_test.go b/objects/objects_test.go deleted file mode 100644 index c411736..0000000 --- a/objects/objects_test.go +++ /dev/null @@ -1,25 +0,0 @@ -package objects_test - -import ( - "testing" - - "github.com/d5/tengo/assert" - "github.com/d5/tengo/compiler/token" - "github.com/d5/tengo/objects" -) - -func testBinaryOp(t *testing.T, lhs objects.Object, op token.Token, rhs objects.Object, expected objects.Object) bool { - t.Helper() - - actual, err := lhs.BinaryOp(op, rhs) - - return assert.NoError(t, err) && assert.Equal(t, expected, actual) -} - -func boolValue(b bool) objects.Object { - if b { - return objects.TrueValue - } - - return objects.FalseValue -} diff --git a/objects/source_module.go b/objects/source_module.go deleted file mode 100644 index 577fddf..0000000 --- a/objects/source_module.go +++ /dev/null @@ -1,11 +0,0 @@ -package objects - -// SourceModule is an importable module that's written in Tengo. -type SourceModule struct { - Src []byte -} - -// Import returns a module source code. -func (m *SourceModule) Import(_ string) (interface{}, error) { - return m.Src, nil -} diff --git a/objects/string.go b/objects/string.go deleted file mode 100644 index c25b050..0000000 --- a/objects/string.go +++ /dev/null @@ -1,103 +0,0 @@ -package objects - -import ( - "strconv" - - "github.com/d5/tengo" - "github.com/d5/tengo/compiler/token" -) - -// String represents a string value. -type String struct { - Value string - runeStr []rune -} - -// TypeName returns the name of the type. -func (o *String) TypeName() string { - return "string" -} - -func (o *String) String() string { - return strconv.Quote(o.Value) -} - -// BinaryOp returns another object that is the result of -// a given binary operator and a right-hand side object. -func (o *String) BinaryOp(op token.Token, rhs Object) (Object, error) { - switch op { - case token.Add: - switch rhs := rhs.(type) { - case *String: - if len(o.Value)+len(rhs.Value) > tengo.MaxStringLen { - return nil, ErrStringLimit - } - return &String{Value: o.Value + rhs.Value}, nil - default: - rhsStr := rhs.String() - if len(o.Value)+len(rhsStr) > tengo.MaxStringLen { - return nil, ErrStringLimit - } - return &String{Value: o.Value + rhsStr}, nil - } - } - - return nil, ErrInvalidOperator -} - -// IsFalsy returns true if the value of the type is falsy. -func (o *String) IsFalsy() bool { - return len(o.Value) == 0 -} - -// Copy returns a copy of the type. -func (o *String) Copy() Object { - return &String{Value: o.Value} -} - -// Equals returns true if the value of the type -// is equal to the value of another object. -func (o *String) Equals(x Object) bool { - t, ok := x.(*String) - if !ok { - return false - } - - return o.Value == t.Value -} - -// IndexGet returns a character at a given index. -func (o *String) IndexGet(index Object) (res Object, err error) { - intIdx, ok := index.(*Int) - if !ok { - err = ErrInvalidIndexType - return - } - - idxVal := int(intIdx.Value) - - if o.runeStr == nil { - o.runeStr = []rune(o.Value) - } - - if idxVal < 0 || idxVal >= len(o.runeStr) { - res = UndefinedValue - return - } - - res = &Char{Value: o.runeStr[idxVal]} - - return -} - -// Iterate creates a string iterator. -func (o *String) Iterate() Iterator { - if o.runeStr == nil { - o.runeStr = []rune(o.Value) - } - - return &StringIterator{ - v: o.runeStr, - l: len(o.runeStr), - } -} diff --git a/objects/string_iterator.go b/objects/string_iterator.go deleted file mode 100644 index 8bc95eb..0000000 --- a/objects/string_iterator.go +++ /dev/null @@ -1,57 +0,0 @@ -package objects - -import "github.com/d5/tengo/compiler/token" - -// StringIterator represents an iterator for a string. -type StringIterator struct { - v []rune - i int - l int -} - -// TypeName returns the name of the type. -func (i *StringIterator) TypeName() string { - return "string-iterator" -} - -func (i *StringIterator) String() string { - return "" -} - -// BinaryOp returns another object that is the result of -// a given binary operator and a right-hand side object. -func (i *StringIterator) BinaryOp(op token.Token, rhs Object) (Object, error) { - return nil, ErrInvalidOperator -} - -// IsFalsy returns true if the value of the type is falsy. -func (i *StringIterator) IsFalsy() bool { - return true -} - -// Equals returns true if the value of the type -// is equal to the value of another object. -func (i *StringIterator) Equals(Object) bool { - return false -} - -// Copy returns a copy of the type. -func (i *StringIterator) Copy() Object { - return &StringIterator{v: i.v, i: i.i, l: i.l} -} - -// Next returns true if there are more elements to iterate. -func (i *StringIterator) Next() bool { - i.i++ - return i.i <= i.l -} - -// Key returns the key or index value of the current element. -func (i *StringIterator) Key() Object { - return &Int{Value: int64(i.i - 1)} -} - -// Value returns the value of the current element. -func (i *StringIterator) Value() Object { - return &Char{Value: i.v[i.i-1]} -} diff --git a/objects/string_test.go b/objects/string_test.go deleted file mode 100644 index e6df899..0000000 --- a/objects/string_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package objects_test - -import ( - "testing" - - "github.com/d5/tengo/compiler/token" - "github.com/d5/tengo/objects" -) - -func TestString_BinaryOp(t *testing.T) { - lstr := "abcde" - rstr := "01234" - for l := 0; l < len(lstr); l++ { - for r := 0; r < len(rstr); r++ { - ls := lstr[l:] - rs := rstr[r:] - testBinaryOp(t, &objects.String{Value: ls}, token.Add, &objects.String{Value: rs}, &objects.String{Value: ls + rs}) - - rc := []rune(rstr)[r] - testBinaryOp(t, &objects.String{Value: ls}, token.Add, &objects.Char{Value: rc}, &objects.String{Value: ls + string(rc)}) - } - } -} diff --git a/objects/time.go b/objects/time.go deleted file mode 100644 index 4e783cc..0000000 --- a/objects/time.go +++ /dev/null @@ -1,89 +0,0 @@ -package objects - -import ( - "time" - - "github.com/d5/tengo/compiler/token" -) - -// Time represents a time value. -type Time struct { - Value time.Time -} - -func (o *Time) String() string { - return o.Value.String() -} - -// TypeName returns the name of the type. -func (o *Time) TypeName() string { - return "time" -} - -// BinaryOp returns another object that is the result of -// a given binary operator and a right-hand side object. -func (o *Time) BinaryOp(op token.Token, rhs Object) (Object, error) { - switch rhs := rhs.(type) { - case *Int: - switch op { - case token.Add: // time + int => time - if rhs.Value == 0 { - return o, nil - } - return &Time{Value: o.Value.Add(time.Duration(rhs.Value))}, nil - case token.Sub: // time - int => time - if rhs.Value == 0 { - return o, nil - } - return &Time{Value: o.Value.Add(time.Duration(-rhs.Value))}, nil - } - case *Time: - switch op { - case token.Sub: // time - time => int (duration) - return &Int{Value: int64(o.Value.Sub(rhs.Value))}, nil - case token.Less: // time < time => bool - if o.Value.Before(rhs.Value) { - return TrueValue, nil - } - return FalseValue, nil - case token.Greater: - if o.Value.After(rhs.Value) { - return TrueValue, nil - } - return FalseValue, nil - case token.LessEq: - if o.Value.Equal(rhs.Value) || o.Value.Before(rhs.Value) { - return TrueValue, nil - } - return FalseValue, nil - case token.GreaterEq: - if o.Value.Equal(rhs.Value) || o.Value.After(rhs.Value) { - return TrueValue, nil - } - return FalseValue, nil - } - } - - return nil, ErrInvalidOperator -} - -// Copy returns a copy of the type. -func (o *Time) Copy() Object { - return &Time{Value: o.Value} -} - -// IsFalsy returns true if the value of the type is falsy. -func (o *Time) IsFalsy() bool { - return o.Value.IsZero() -} - -// Equals returns true if the value of the type -// is equal to the value of another object. -func (o *Time) Equals(x Object) bool { - t, ok := x.(*Time) - if !ok { - return false - } - - return o.Value.Equal(t.Value) -} diff --git a/objects/undefined.go b/objects/undefined.go deleted file mode 100644 index 0fdbc08..0000000 --- a/objects/undefined.go +++ /dev/null @@ -1,62 +0,0 @@ -package objects - -import "github.com/d5/tengo/compiler/token" - -// Undefined represents an undefined value. -type Undefined struct{} - -// TypeName returns the name of the type. -func (o *Undefined) TypeName() string { - return "undefined" -} - -func (o *Undefined) String() string { - return "" -} - -// BinaryOp returns another object that is the result of -// a given binary operator and a right-hand side object. -func (o *Undefined) BinaryOp(op token.Token, rhs Object) (Object, error) { - return nil, ErrInvalidOperator -} - -// Copy returns a copy of the type. -func (o *Undefined) Copy() Object { - return o -} - -// IsFalsy returns true if the value of the type is falsy. -func (o *Undefined) IsFalsy() bool { - return true -} - -// Equals returns true if the value of the type -// is equal to the value of another object. -func (o *Undefined) Equals(x Object) bool { - return o == x -} - -// IndexGet returns an element at a given index. -func (o *Undefined) IndexGet(index Object) (Object, error) { - return UndefinedValue, nil -} - -// Iterate creates a map iterator. -func (o *Undefined) Iterate() Iterator { - return o -} - -// Next returns true if there are more elements to iterate. -func (o *Undefined) Next() bool { - return false -} - -// Key returns the key or index value of the current element. -func (o *Undefined) Key() Object { - return o -} - -// Value returns the value of the current element. -func (o *Undefined) Value() Object { - return o -} diff --git a/objects/user_function.go b/objects/user_function.go deleted file mode 100644 index a896788..0000000 --- a/objects/user_function.go +++ /dev/null @@ -1,48 +0,0 @@ -package objects - -import ( - "github.com/d5/tengo/compiler/token" -) - -// UserFunction represents a user function. -type UserFunction struct { - Name string - Value CallableFunc - EncodingID string -} - -// TypeName returns the name of the type. -func (o *UserFunction) TypeName() string { - return "user-function:" + o.Name -} - -func (o *UserFunction) String() string { - return "" -} - -// BinaryOp returns another object that is the result of -// a given binary operator and a right-hand side object. -func (o *UserFunction) BinaryOp(op token.Token, rhs Object) (Object, error) { - return nil, ErrInvalidOperator -} - -// Copy returns a copy of the type. -func (o *UserFunction) Copy() Object { - return &UserFunction{Value: o.Value} -} - -// IsFalsy returns true if the value of the type is falsy. -func (o *UserFunction) IsFalsy() bool { - return false -} - -// Equals returns true if the value of the type -// is equal to the value of another object. -func (o *UserFunction) Equals(x Object) bool { - return false -} - -// Call invokes a user function. -func (o *UserFunction) Call(args ...Object) (Object, error) { - return o.Value(args...) -} diff --git a/objects_test.go b/objects_test.go new file mode 100644 index 0000000..3363902 --- /dev/null +++ b/objects_test.go @@ -0,0 +1,736 @@ +package tengo_test + +import ( + "testing" + + "github.com/d5/tengo" + "github.com/d5/tengo/internal/require" + "github.com/d5/tengo/internal/token" +) + +func TestObject_TypeName(t *testing.T) { + var o tengo.Object = &tengo.Int{} + require.Equal(t, "int", o.TypeName()) + o = &tengo.Float{} + require.Equal(t, "float", o.TypeName()) + o = &tengo.Char{} + require.Equal(t, "char", o.TypeName()) + o = &tengo.String{} + require.Equal(t, "string", o.TypeName()) + o = &tengo.Bool{} + require.Equal(t, "bool", o.TypeName()) + o = &tengo.Array{} + require.Equal(t, "array", o.TypeName()) + o = &tengo.Map{} + require.Equal(t, "map", o.TypeName()) + o = &tengo.ArrayIterator{} + require.Equal(t, "array-iterator", o.TypeName()) + o = &tengo.StringIterator{} + require.Equal(t, "string-iterator", o.TypeName()) + o = &tengo.MapIterator{} + require.Equal(t, "map-iterator", o.TypeName()) + o = &tengo.BuiltinFunction{Name: "fn"} + require.Equal(t, "builtin-function:fn", o.TypeName()) + o = &tengo.UserFunction{Name: "fn"} + require.Equal(t, "user-function:fn", o.TypeName()) + o = &tengo.CompiledFunction{} + require.Equal(t, "compiled-function", o.TypeName()) + o = &tengo.Undefined{} + require.Equal(t, "undefined", o.TypeName()) + o = &tengo.Error{} + require.Equal(t, "error", o.TypeName()) + o = &tengo.Bytes{} + require.Equal(t, "bytes", o.TypeName()) +} + +func TestObject_IsFalsy(t *testing.T) { + var o tengo.Object = &tengo.Int{Value: 0} + require.True(t, o.IsFalsy()) + o = &tengo.Int{Value: 1} + require.False(t, o.IsFalsy()) + o = &tengo.Float{Value: 0} + require.False(t, o.IsFalsy()) + o = &tengo.Float{Value: 1} + require.False(t, o.IsFalsy()) + o = &tengo.Char{Value: ' '} + require.False(t, o.IsFalsy()) + o = &tengo.Char{Value: 'T'} + require.False(t, o.IsFalsy()) + o = &tengo.String{Value: ""} + require.True(t, o.IsFalsy()) + o = &tengo.String{Value: " "} + require.False(t, o.IsFalsy()) + o = &tengo.Array{Value: nil} + require.True(t, o.IsFalsy()) + o = &tengo.Array{Value: []tengo.Object{nil}} // nil is not valid but still count as 1 element + require.False(t, o.IsFalsy()) + o = &tengo.Map{Value: nil} + require.True(t, o.IsFalsy()) + o = &tengo.Map{Value: map[string]tengo.Object{"a": nil}} // nil is not valid but still count as 1 element + require.False(t, o.IsFalsy()) + o = &tengo.StringIterator{} + require.True(t, o.IsFalsy()) + o = &tengo.ArrayIterator{} + require.True(t, o.IsFalsy()) + o = &tengo.MapIterator{} + require.True(t, o.IsFalsy()) + o = &tengo.BuiltinFunction{} + require.False(t, o.IsFalsy()) + o = &tengo.CompiledFunction{} + require.False(t, o.IsFalsy()) + o = &tengo.Undefined{} + require.True(t, o.IsFalsy()) + o = &tengo.Error{} + require.True(t, o.IsFalsy()) + o = &tengo.Bytes{} + require.True(t, o.IsFalsy()) + o = &tengo.Bytes{Value: []byte{1, 2}} + require.False(t, o.IsFalsy()) +} + +func TestObject_String(t *testing.T) { + var o tengo.Object = &tengo.Int{Value: 0} + require.Equal(t, "0", o.String()) + o = &tengo.Int{Value: 1} + require.Equal(t, "1", o.String()) + o = &tengo.Float{Value: 0} + require.Equal(t, "0", o.String()) + o = &tengo.Float{Value: 1} + require.Equal(t, "1", o.String()) + o = &tengo.Char{Value: ' '} + require.Equal(t, " ", o.String()) + o = &tengo.Char{Value: 'T'} + require.Equal(t, "T", o.String()) + o = &tengo.String{Value: ""} + require.Equal(t, `""`, o.String()) + o = &tengo.String{Value: " "} + require.Equal(t, `" "`, o.String()) + o = &tengo.Array{Value: nil} + require.Equal(t, "[]", o.String()) + o = &tengo.Map{Value: nil} + require.Equal(t, "{}", o.String()) + o = &tengo.Error{Value: nil} + require.Equal(t, "error", o.String()) + o = &tengo.Error{Value: &tengo.String{Value: "error 1"}} + require.Equal(t, `error: "error 1"`, o.String()) + o = &tengo.StringIterator{} + require.Equal(t, "", o.String()) + o = &tengo.ArrayIterator{} + require.Equal(t, "", o.String()) + o = &tengo.MapIterator{} + require.Equal(t, "", o.String()) + o = &tengo.Undefined{} + require.Equal(t, "", o.String()) + o = &tengo.Bytes{} + require.Equal(t, "", o.String()) + o = &tengo.Bytes{Value: []byte("foo")} + require.Equal(t, "foo", o.String()) +} + +func TestObject_BinaryOp(t *testing.T) { + var o tengo.Object = &tengo.Char{} + _, err := o.BinaryOp(token.Add, tengo.UndefinedValue) + require.Error(t, err) + o = &tengo.Bool{} + _, err = o.BinaryOp(token.Add, tengo.UndefinedValue) + require.Error(t, err) + o = &tengo.Map{} + _, err = o.BinaryOp(token.Add, tengo.UndefinedValue) + require.Error(t, err) + o = &tengo.ArrayIterator{} + _, err = o.BinaryOp(token.Add, tengo.UndefinedValue) + require.Error(t, err) + o = &tengo.StringIterator{} + _, err = o.BinaryOp(token.Add, tengo.UndefinedValue) + require.Error(t, err) + o = &tengo.MapIterator{} + _, err = o.BinaryOp(token.Add, tengo.UndefinedValue) + require.Error(t, err) + o = &tengo.BuiltinFunction{} + _, err = o.BinaryOp(token.Add, tengo.UndefinedValue) + require.Error(t, err) + o = &tengo.CompiledFunction{} + _, err = o.BinaryOp(token.Add, tengo.UndefinedValue) + require.Error(t, err) + o = &tengo.Undefined{} + _, err = o.BinaryOp(token.Add, tengo.UndefinedValue) + require.Error(t, err) + o = &tengo.Error{} + _, err = o.BinaryOp(token.Add, tengo.UndefinedValue) + require.Error(t, err) +} + +func TestArray_BinaryOp(t *testing.T) { + testBinaryOp(t, &tengo.Array{Value: nil}, token.Add, + &tengo.Array{Value: nil}, &tengo.Array{Value: nil}) + testBinaryOp(t, &tengo.Array{Value: nil}, token.Add, + &tengo.Array{Value: []tengo.Object{}}, &tengo.Array{Value: nil}) + testBinaryOp(t, &tengo.Array{Value: []tengo.Object{}}, token.Add, + &tengo.Array{Value: nil}, &tengo.Array{Value: []tengo.Object{}}) + testBinaryOp(t, &tengo.Array{Value: []tengo.Object{}}, token.Add, + &tengo.Array{Value: []tengo.Object{}}, + &tengo.Array{Value: []tengo.Object{}}) + testBinaryOp(t, &tengo.Array{Value: nil}, token.Add, + &tengo.Array{Value: []tengo.Object{ + &tengo.Int{Value: 1}, + }}, &tengo.Array{Value: []tengo.Object{ + &tengo.Int{Value: 1}, + }}) + testBinaryOp(t, &tengo.Array{Value: nil}, token.Add, + &tengo.Array{Value: []tengo.Object{ + &tengo.Int{Value: 1}, + &tengo.Int{Value: 2}, + &tengo.Int{Value: 3}, + }}, &tengo.Array{Value: []tengo.Object{ + &tengo.Int{Value: 1}, + &tengo.Int{Value: 2}, + &tengo.Int{Value: 3}, + }}) + testBinaryOp(t, &tengo.Array{Value: []tengo.Object{ + &tengo.Int{Value: 1}, + &tengo.Int{Value: 2}, + &tengo.Int{Value: 3}, + }}, token.Add, &tengo.Array{Value: nil}, + &tengo.Array{Value: []tengo.Object{ + &tengo.Int{Value: 1}, + &tengo.Int{Value: 2}, + &tengo.Int{Value: 3}, + }}) + testBinaryOp(t, &tengo.Array{Value: []tengo.Object{ + &tengo.Int{Value: 1}, + &tengo.Int{Value: 2}, + &tengo.Int{Value: 3}, + }}, token.Add, &tengo.Array{Value: []tengo.Object{ + &tengo.Int{Value: 4}, + &tengo.Int{Value: 5}, + &tengo.Int{Value: 6}, + }}, &tengo.Array{Value: []tengo.Object{ + &tengo.Int{Value: 1}, + &tengo.Int{Value: 2}, + &tengo.Int{Value: 3}, + &tengo.Int{Value: 4}, + &tengo.Int{Value: 5}, + &tengo.Int{Value: 6}, + }}) +} + +func TestError_Equals(t *testing.T) { + err1 := &tengo.Error{Value: &tengo.String{Value: "some error"}} + err2 := err1 + require.True(t, err1.Equals(err2)) + require.True(t, err2.Equals(err1)) + + err2 = &tengo.Error{Value: &tengo.String{Value: "some error"}} + require.False(t, err1.Equals(err2)) + require.False(t, err2.Equals(err1)) +} + +func TestFloat_BinaryOp(t *testing.T) { + // float + float + for l := float64(-2); l <= 2.1; l += 0.4 { + for r := float64(-2); r <= 2.1; r += 0.4 { + testBinaryOp(t, &tengo.Float{Value: l}, token.Add, + &tengo.Float{Value: r}, &tengo.Float{Value: l + r}) + } + } + + // float - float + for l := float64(-2); l <= 2.1; l += 0.4 { + for r := float64(-2); r <= 2.1; r += 0.4 { + testBinaryOp(t, &tengo.Float{Value: l}, token.Sub, + &tengo.Float{Value: r}, &tengo.Float{Value: l - r}) + } + } + + // float * float + for l := float64(-2); l <= 2.1; l += 0.4 { + for r := float64(-2); r <= 2.1; r += 0.4 { + testBinaryOp(t, &tengo.Float{Value: l}, token.Mul, + &tengo.Float{Value: r}, &tengo.Float{Value: l * r}) + } + } + + // float / float + for l := float64(-2); l <= 2.1; l += 0.4 { + for r := float64(-2); r <= 2.1; r += 0.4 { + if r != 0 { + testBinaryOp(t, &tengo.Float{Value: l}, token.Quo, + &tengo.Float{Value: r}, &tengo.Float{Value: l / r}) + } + } + } + + // float < float + for l := float64(-2); l <= 2.1; l += 0.4 { + for r := float64(-2); r <= 2.1; r += 0.4 { + testBinaryOp(t, &tengo.Float{Value: l}, token.Less, + &tengo.Float{Value: r}, boolValue(l < r)) + } + } + + // float > float + for l := float64(-2); l <= 2.1; l += 0.4 { + for r := float64(-2); r <= 2.1; r += 0.4 { + testBinaryOp(t, &tengo.Float{Value: l}, token.Greater, + &tengo.Float{Value: r}, boolValue(l > r)) + } + } + + // float <= float + for l := float64(-2); l <= 2.1; l += 0.4 { + for r := float64(-2); r <= 2.1; r += 0.4 { + testBinaryOp(t, &tengo.Float{Value: l}, token.LessEq, + &tengo.Float{Value: r}, boolValue(l <= r)) + } + } + + // float >= float + for l := float64(-2); l <= 2.1; l += 0.4 { + for r := float64(-2); r <= 2.1; r += 0.4 { + testBinaryOp(t, &tengo.Float{Value: l}, token.GreaterEq, + &tengo.Float{Value: r}, boolValue(l >= r)) + } + } + + // float + int + for l := float64(-2); l <= 2.1; l += 0.4 { + for r := int64(-2); r <= 2; r++ { + testBinaryOp(t, &tengo.Float{Value: l}, token.Add, + &tengo.Int{Value: r}, &tengo.Float{Value: l + float64(r)}) + } + } + + // float - int + for l := float64(-2); l <= 2.1; l += 0.4 { + for r := int64(-2); r <= 2; r++ { + testBinaryOp(t, &tengo.Float{Value: l}, token.Sub, + &tengo.Int{Value: r}, &tengo.Float{Value: l - float64(r)}) + } + } + + // float * int + for l := float64(-2); l <= 2.1; l += 0.4 { + for r := int64(-2); r <= 2; r++ { + testBinaryOp(t, &tengo.Float{Value: l}, token.Mul, + &tengo.Int{Value: r}, &tengo.Float{Value: l * float64(r)}) + } + } + + // float / int + for l := float64(-2); l <= 2.1; l += 0.4 { + for r := int64(-2); r <= 2; r++ { + if r != 0 { + testBinaryOp(t, &tengo.Float{Value: l}, token.Quo, + &tengo.Int{Value: r}, + &tengo.Float{Value: l / float64(r)}) + } + } + } + + // float < int + for l := float64(-2); l <= 2.1; l += 0.4 { + for r := int64(-2); r <= 2; r++ { + testBinaryOp(t, &tengo.Float{Value: l}, token.Less, + &tengo.Int{Value: r}, boolValue(l < float64(r))) + } + } + + // float > int + for l := float64(-2); l <= 2.1; l += 0.4 { + for r := int64(-2); r <= 2; r++ { + testBinaryOp(t, &tengo.Float{Value: l}, token.Greater, + &tengo.Int{Value: r}, boolValue(l > float64(r))) + } + } + + // float <= int + for l := float64(-2); l <= 2.1; l += 0.4 { + for r := int64(-2); r <= 2; r++ { + testBinaryOp(t, &tengo.Float{Value: l}, token.LessEq, + &tengo.Int{Value: r}, boolValue(l <= float64(r))) + } + } + + // float >= int + for l := float64(-2); l <= 2.1; l += 0.4 { + for r := int64(-2); r <= 2; r++ { + testBinaryOp(t, &tengo.Float{Value: l}, token.GreaterEq, + &tengo.Int{Value: r}, boolValue(l >= float64(r))) + } + } +} + +func TestInt_BinaryOp(t *testing.T) { + // int + int + for l := int64(-2); l <= 2; l++ { + for r := int64(-2); r <= 2; r++ { + testBinaryOp(t, &tengo.Int{Value: l}, token.Add, + &tengo.Int{Value: r}, &tengo.Int{Value: l + r}) + } + } + + // int - int + for l := int64(-2); l <= 2; l++ { + for r := int64(-2); r <= 2; r++ { + testBinaryOp(t, &tengo.Int{Value: l}, token.Sub, + &tengo.Int{Value: r}, &tengo.Int{Value: l - r}) + } + } + + // int * int + for l := int64(-2); l <= 2; l++ { + for r := int64(-2); r <= 2; r++ { + testBinaryOp(t, &tengo.Int{Value: l}, token.Mul, + &tengo.Int{Value: r}, &tengo.Int{Value: l * r}) + } + } + + // int / int + for l := int64(-2); l <= 2; l++ { + for r := int64(-2); r <= 2; r++ { + if r != 0 { + testBinaryOp(t, &tengo.Int{Value: l}, token.Quo, + &tengo.Int{Value: r}, &tengo.Int{Value: l / r}) + } + } + } + + // int % int + for l := int64(-4); l <= 4; l++ { + for r := -int64(-4); r <= 4; r++ { + if r == 0 { + testBinaryOp(t, &tengo.Int{Value: l}, token.Rem, + &tengo.Int{Value: r}, &tengo.Int{Value: l % r}) + } + } + } + + // int & int + testBinaryOp(t, + &tengo.Int{Value: 0}, token.And, &tengo.Int{Value: 0}, + &tengo.Int{Value: int64(0)}) + testBinaryOp(t, + &tengo.Int{Value: 1}, token.And, &tengo.Int{Value: 0}, + &tengo.Int{Value: int64(1) & int64(0)}) + testBinaryOp(t, + &tengo.Int{Value: 0}, token.And, &tengo.Int{Value: 1}, + &tengo.Int{Value: int64(0) & int64(1)}) + testBinaryOp(t, + &tengo.Int{Value: 1}, token.And, &tengo.Int{Value: 1}, + &tengo.Int{Value: int64(1)}) + testBinaryOp(t, + &tengo.Int{Value: 0}, token.And, &tengo.Int{Value: int64(0xffffffff)}, + &tengo.Int{Value: int64(0) & int64(0xffffffff)}) + testBinaryOp(t, + &tengo.Int{Value: 1}, token.And, &tengo.Int{Value: int64(0xffffffff)}, + &tengo.Int{Value: int64(1) & int64(0xffffffff)}) + testBinaryOp(t, + &tengo.Int{Value: int64(0xffffffff)}, token.And, + &tengo.Int{Value: int64(0xffffffff)}, + &tengo.Int{Value: int64(0xffffffff)}) + testBinaryOp(t, + &tengo.Int{Value: 1984}, token.And, + &tengo.Int{Value: int64(0xffffffff)}, + &tengo.Int{Value: int64(1984) & int64(0xffffffff)}) + testBinaryOp(t, &tengo.Int{Value: -1984}, token.And, + &tengo.Int{Value: int64(0xffffffff)}, + &tengo.Int{Value: int64(-1984) & int64(0xffffffff)}) + + // int | int + testBinaryOp(t, + &tengo.Int{Value: 0}, token.Or, &tengo.Int{Value: 0}, + &tengo.Int{Value: int64(0)}) + testBinaryOp(t, + &tengo.Int{Value: 1}, token.Or, &tengo.Int{Value: 0}, + &tengo.Int{Value: int64(1) | int64(0)}) + testBinaryOp(t, + &tengo.Int{Value: 0}, token.Or, &tengo.Int{Value: 1}, + &tengo.Int{Value: int64(0) | int64(1)}) + testBinaryOp(t, + &tengo.Int{Value: 1}, token.Or, &tengo.Int{Value: 1}, + &tengo.Int{Value: int64(1)}) + testBinaryOp(t, + &tengo.Int{Value: 0}, token.Or, &tengo.Int{Value: int64(0xffffffff)}, + &tengo.Int{Value: int64(0) | int64(0xffffffff)}) + testBinaryOp(t, + &tengo.Int{Value: 1}, token.Or, &tengo.Int{Value: int64(0xffffffff)}, + &tengo.Int{Value: int64(1) | int64(0xffffffff)}) + testBinaryOp(t, + &tengo.Int{Value: int64(0xffffffff)}, token.Or, + &tengo.Int{Value: int64(0xffffffff)}, + &tengo.Int{Value: int64(0xffffffff)}) + testBinaryOp(t, + &tengo.Int{Value: 1984}, token.Or, + &tengo.Int{Value: int64(0xffffffff)}, + &tengo.Int{Value: int64(1984) | int64(0xffffffff)}) + testBinaryOp(t, + &tengo.Int{Value: -1984}, token.Or, + &tengo.Int{Value: int64(0xffffffff)}, + &tengo.Int{Value: int64(-1984) | int64(0xffffffff)}) + + // int ^ int + testBinaryOp(t, + &tengo.Int{Value: 0}, token.Xor, &tengo.Int{Value: 0}, + &tengo.Int{Value: int64(0)}) + testBinaryOp(t, + &tengo.Int{Value: 1}, token.Xor, &tengo.Int{Value: 0}, + &tengo.Int{Value: int64(1) ^ int64(0)}) + testBinaryOp(t, + &tengo.Int{Value: 0}, token.Xor, &tengo.Int{Value: 1}, + &tengo.Int{Value: int64(0) ^ int64(1)}) + testBinaryOp(t, + &tengo.Int{Value: 1}, token.Xor, &tengo.Int{Value: 1}, + &tengo.Int{Value: int64(0)}) + testBinaryOp(t, + &tengo.Int{Value: 0}, token.Xor, &tengo.Int{Value: int64(0xffffffff)}, + &tengo.Int{Value: int64(0) ^ int64(0xffffffff)}) + testBinaryOp(t, + &tengo.Int{Value: 1}, token.Xor, &tengo.Int{Value: int64(0xffffffff)}, + &tengo.Int{Value: int64(1) ^ int64(0xffffffff)}) + testBinaryOp(t, + &tengo.Int{Value: int64(0xffffffff)}, token.Xor, + &tengo.Int{Value: int64(0xffffffff)}, + &tengo.Int{Value: int64(0)}) + testBinaryOp(t, + &tengo.Int{Value: 1984}, token.Xor, + &tengo.Int{Value: int64(0xffffffff)}, + &tengo.Int{Value: int64(1984) ^ int64(0xffffffff)}) + testBinaryOp(t, + &tengo.Int{Value: -1984}, token.Xor, + &tengo.Int{Value: int64(0xffffffff)}, + &tengo.Int{Value: int64(-1984) ^ int64(0xffffffff)}) + + // int &^ int + testBinaryOp(t, + &tengo.Int{Value: 0}, token.AndNot, &tengo.Int{Value: 0}, + &tengo.Int{Value: int64(0)}) + testBinaryOp(t, + &tengo.Int{Value: 1}, token.AndNot, &tengo.Int{Value: 0}, + &tengo.Int{Value: int64(1) &^ int64(0)}) + testBinaryOp(t, + &tengo.Int{Value: 0}, token.AndNot, + &tengo.Int{Value: 1}, &tengo.Int{Value: int64(0) &^ int64(1)}) + testBinaryOp(t, + &tengo.Int{Value: 1}, token.AndNot, &tengo.Int{Value: 1}, + &tengo.Int{Value: int64(0)}) + testBinaryOp(t, + &tengo.Int{Value: 0}, token.AndNot, + &tengo.Int{Value: int64(0xffffffff)}, + &tengo.Int{Value: int64(0) &^ int64(0xffffffff)}) + testBinaryOp(t, + &tengo.Int{Value: 1}, token.AndNot, + &tengo.Int{Value: int64(0xffffffff)}, + &tengo.Int{Value: int64(1) &^ int64(0xffffffff)}) + testBinaryOp(t, + &tengo.Int{Value: int64(0xffffffff)}, token.AndNot, + &tengo.Int{Value: int64(0xffffffff)}, + &tengo.Int{Value: int64(0)}) + testBinaryOp(t, + &tengo.Int{Value: 1984}, token.AndNot, + &tengo.Int{Value: int64(0xffffffff)}, + &tengo.Int{Value: int64(1984) &^ int64(0xffffffff)}) + testBinaryOp(t, + &tengo.Int{Value: -1984}, token.AndNot, + &tengo.Int{Value: int64(0xffffffff)}, + &tengo.Int{Value: int64(-1984) &^ int64(0xffffffff)}) + + // int << int + for s := int64(0); s < 64; s++ { + testBinaryOp(t, + &tengo.Int{Value: 0}, token.Shl, &tengo.Int{Value: s}, + &tengo.Int{Value: int64(0) << uint(s)}) + testBinaryOp(t, + &tengo.Int{Value: 1}, token.Shl, &tengo.Int{Value: s}, + &tengo.Int{Value: int64(1) << uint(s)}) + testBinaryOp(t, + &tengo.Int{Value: 2}, token.Shl, &tengo.Int{Value: s}, + &tengo.Int{Value: int64(2) << uint(s)}) + testBinaryOp(t, + &tengo.Int{Value: -1}, token.Shl, &tengo.Int{Value: s}, + &tengo.Int{Value: int64(-1) << uint(s)}) + testBinaryOp(t, + &tengo.Int{Value: -2}, token.Shl, &tengo.Int{Value: s}, + &tengo.Int{Value: int64(-2) << uint(s)}) + testBinaryOp(t, + &tengo.Int{Value: int64(0xffffffff)}, token.Shl, + &tengo.Int{Value: s}, + &tengo.Int{Value: int64(0xffffffff) << uint(s)}) + } + + // int >> int + for s := int64(0); s < 64; s++ { + testBinaryOp(t, + &tengo.Int{Value: 0}, token.Shr, &tengo.Int{Value: s}, + &tengo.Int{Value: int64(0) >> uint(s)}) + testBinaryOp(t, + &tengo.Int{Value: 1}, token.Shr, &tengo.Int{Value: s}, + &tengo.Int{Value: int64(1) >> uint(s)}) + testBinaryOp(t, + &tengo.Int{Value: 2}, token.Shr, &tengo.Int{Value: s}, + &tengo.Int{Value: int64(2) >> uint(s)}) + testBinaryOp(t, + &tengo.Int{Value: -1}, token.Shr, &tengo.Int{Value: s}, + &tengo.Int{Value: int64(-1) >> uint(s)}) + testBinaryOp(t, + &tengo.Int{Value: -2}, token.Shr, &tengo.Int{Value: s}, + &tengo.Int{Value: int64(-2) >> uint(s)}) + testBinaryOp(t, + &tengo.Int{Value: int64(0xffffffff)}, token.Shr, + &tengo.Int{Value: s}, + &tengo.Int{Value: int64(0xffffffff) >> uint(s)}) + } + + // int < int + for l := int64(-2); l <= 2; l++ { + for r := int64(-2); r <= 2; r++ { + testBinaryOp(t, &tengo.Int{Value: l}, token.Less, + &tengo.Int{Value: r}, boolValue(l < r)) + } + } + + // int > int + for l := int64(-2); l <= 2; l++ { + for r := int64(-2); r <= 2; r++ { + testBinaryOp(t, &tengo.Int{Value: l}, token.Greater, + &tengo.Int{Value: r}, boolValue(l > r)) + } + } + + // int <= int + for l := int64(-2); l <= 2; l++ { + for r := int64(-2); r <= 2; r++ { + testBinaryOp(t, &tengo.Int{Value: l}, token.LessEq, + &tengo.Int{Value: r}, boolValue(l <= r)) + } + } + + // int >= int + for l := int64(-2); l <= 2; l++ { + for r := int64(-2); r <= 2; r++ { + testBinaryOp(t, &tengo.Int{Value: l}, token.GreaterEq, + &tengo.Int{Value: r}, boolValue(l >= r)) + } + } + + // int + float + for l := int64(-2); l <= 2; l++ { + for r := float64(-2); r <= 2.1; r += 0.5 { + testBinaryOp(t, &tengo.Int{Value: l}, token.Add, + &tengo.Float{Value: r}, + &tengo.Float{Value: float64(l) + r}) + } + } + + // int - float + for l := int64(-2); l <= 2; l++ { + for r := float64(-2); r <= 2.1; r += 0.5 { + testBinaryOp(t, &tengo.Int{Value: l}, token.Sub, + &tengo.Float{Value: r}, + &tengo.Float{Value: float64(l) - r}) + } + } + + // int * float + for l := int64(-2); l <= 2; l++ { + for r := float64(-2); r <= 2.1; r += 0.5 { + testBinaryOp(t, &tengo.Int{Value: l}, token.Mul, + &tengo.Float{Value: r}, + &tengo.Float{Value: float64(l) * r}) + } + } + + // int / float + for l := int64(-2); l <= 2; l++ { + for r := float64(-2); r <= 2.1; r += 0.5 { + if r != 0 { + testBinaryOp(t, &tengo.Int{Value: l}, token.Quo, + &tengo.Float{Value: r}, + &tengo.Float{Value: float64(l) / r}) + } + } + } + + // int < float + for l := int64(-2); l <= 2; l++ { + for r := float64(-2); r <= 2.1; r += 0.5 { + testBinaryOp(t, &tengo.Int{Value: l}, token.Less, + &tengo.Float{Value: r}, boolValue(float64(l) < r)) + } + } + + // int > float + for l := int64(-2); l <= 2; l++ { + for r := float64(-2); r <= 2.1; r += 0.5 { + testBinaryOp(t, &tengo.Int{Value: l}, token.Greater, + &tengo.Float{Value: r}, boolValue(float64(l) > r)) + } + } + + // int <= float + for l := int64(-2); l <= 2; l++ { + for r := float64(-2); r <= 2.1; r += 0.5 { + testBinaryOp(t, &tengo.Int{Value: l}, token.LessEq, + &tengo.Float{Value: r}, boolValue(float64(l) <= r)) + } + } + + // int >= float + for l := int64(-2); l <= 2; l++ { + for r := float64(-2); r <= 2.1; r += 0.5 { + testBinaryOp(t, &tengo.Int{Value: l}, token.GreaterEq, + &tengo.Float{Value: r}, boolValue(float64(l) >= r)) + } + } +} + +func TestMap_Index(t *testing.T) { + m := &tengo.Map{Value: make(map[string]tengo.Object)} + k := &tengo.Int{Value: 1} + v := &tengo.String{Value: "abcdef"} + err := m.IndexSet(k, v) + + require.NoError(t, err) + + res, err := m.IndexGet(k) + require.NoError(t, err) + require.Equal(t, v, res) +} + +func TestString_BinaryOp(t *testing.T) { + lstr := "abcde" + rstr := "01234" + for l := 0; l < len(lstr); l++ { + for r := 0; r < len(rstr); r++ { + ls := lstr[l:] + rs := rstr[r:] + testBinaryOp(t, &tengo.String{Value: ls}, token.Add, + &tengo.String{Value: rs}, + &tengo.String{Value: ls + rs}) + + rc := []rune(rstr)[r] + testBinaryOp(t, &tengo.String{Value: ls}, token.Add, + &tengo.Char{Value: rc}, + &tengo.String{Value: ls + string(rc)}) + } + } +} + +func testBinaryOp( + t *testing.T, + lhs tengo.Object, + op token.Token, + rhs tengo.Object, + expected tengo.Object, +) { + t.Helper() + actual, err := lhs.BinaryOp(op, rhs) + require.NoError(t, err) + require.Equal(t, expected, actual) +} + +func boolValue(b bool) tengo.Object { + if b { + return tengo.TrueValue + } + return tengo.FalseValue +} diff --git a/runtime/errors.go b/runtime/errors.go deleted file mode 100644 index fc7ca0e..0000000 --- a/runtime/errors.go +++ /dev/null @@ -1,11 +0,0 @@ -package runtime - -import ( - "errors" -) - -// ErrStackOverflow is a stack overflow error. -var ErrStackOverflow = errors.New("stack overflow") - -// ErrObjectAllocLimit is an objects allocation limit error. -var ErrObjectAllocLimit = errors.New("object allocation limit exceeded") diff --git a/runtime/frame.go b/runtime/frame.go deleted file mode 100644 index cbaadbd..0000000 --- a/runtime/frame.go +++ /dev/null @@ -1,13 +0,0 @@ -package runtime - -import ( - "github.com/d5/tengo/objects" -) - -// Frame represents a function call frame. -type Frame struct { - fn *objects.CompiledFunction - freeVars []*objects.ObjectPtr - ip int - basePointer int -} diff --git a/runtime/vm_array_test.go b/runtime/vm_array_test.go deleted file mode 100644 index dcdf7b4..0000000 --- a/runtime/vm_array_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package runtime_test - -import ( - "fmt" - "testing" - - "github.com/d5/tengo/objects" -) - -func TestArray(t *testing.T) { - expect(t, `out = [1, 2 * 2, 3 + 3]`, nil, ARR{1, 4, 6}) - - // array copy-by-reference - expect(t, `a1 := [1, 2, 3]; a2 := a1; a1[0] = 5; out = a2`, nil, ARR{5, 2, 3}) - expect(t, `func () { a1 := [1, 2, 3]; a2 := a1; a1[0] = 5; out = a2 }()`, nil, ARR{5, 2, 3}) - - // array index set - expectError(t, `a1 := [1, 2, 3]; a1[3] = 5`, nil, "index out of bounds") - - // index operator - arr := ARR{1, 2, 3, 4, 5, 6} - arrStr := `[1, 2, 3, 4, 5, 6]` - arrLen := 6 - for idx := 0; idx < arrLen; idx++ { - expect(t, fmt.Sprintf("out = %s[%d]", arrStr, idx), nil, arr[idx]) - expect(t, fmt.Sprintf("out = %s[0 + %d]", arrStr, idx), nil, arr[idx]) - expect(t, fmt.Sprintf("out = %s[1 + %d - 1]", arrStr, idx), nil, arr[idx]) - expect(t, fmt.Sprintf("idx := %d; out = %s[idx]", idx, arrStr), nil, arr[idx]) - } - - expect(t, fmt.Sprintf("%s[%d]", arrStr, -1), nil, objects.UndefinedValue) - expect(t, fmt.Sprintf("%s[%d]", arrStr, arrLen), nil, objects.UndefinedValue) - - // slice operator - for low := 0; low < arrLen; low++ { - expect(t, fmt.Sprintf("out = %s[%d:%d]", arrStr, low, low), nil, ARR{}) - for high := low; high <= arrLen; high++ { - expect(t, fmt.Sprintf("out = %s[%d:%d]", arrStr, low, high), nil, arr[low:high]) - expect(t, fmt.Sprintf("out = %s[0 + %d : 0 + %d]", arrStr, low, high), nil, arr[low:high]) - expect(t, fmt.Sprintf("out = %s[1 + %d - 1 : 1 + %d - 1]", arrStr, low, high), nil, arr[low:high]) - expect(t, fmt.Sprintf("out = %s[:%d]", arrStr, high), nil, arr[:high]) - expect(t, fmt.Sprintf("out = %s[%d:]", arrStr, low), nil, arr[low:]) - } - } - - expect(t, fmt.Sprintf("out = %s[:]", arrStr), nil, arr) - expect(t, fmt.Sprintf("out = %s[%d:]", arrStr, -1), nil, arr) - expect(t, fmt.Sprintf("out = %s[:%d]", arrStr, arrLen+1), nil, arr) - expect(t, fmt.Sprintf("out = %s[%d:%d]", arrStr, 2, 2), nil, ARR{}) - - expectError(t, fmt.Sprintf("%s[:%d]", arrStr, -1), nil, "invalid slice index") - expectError(t, fmt.Sprintf("%s[%d:]", arrStr, arrLen+1), nil, "invalid slice index") - expectError(t, fmt.Sprintf("%s[%d:%d]", arrStr, 0, -1), nil, "invalid slice index") - expectError(t, fmt.Sprintf("%s[%d:%d]", arrStr, 2, 1), nil, "invalid slice index") -} diff --git a/runtime/vm_assignment_test.go b/runtime/vm_assignment_test.go deleted file mode 100644 index 46b0625..0000000 --- a/runtime/vm_assignment_test.go +++ /dev/null @@ -1,267 +0,0 @@ -package runtime_test - -import ( - "testing" -) - -func TestAssignment(t *testing.T) { - expect(t, `a := 1; a = 2; out = a`, nil, 2) - expect(t, `a := 1; a = 2; out = a`, nil, 2) - expect(t, `a := 1; a = a + 4; out = a`, nil, 5) - expect(t, `a := 1; f1 := func() { a = 2; return a }; out = f1()`, nil, 2) - expect(t, `a := 1; f1 := func() { a := 3; a = 2; return a }; out = f1()`, nil, 2) - - expect(t, `a := 1; out = a`, nil, 1) - expect(t, `a := 1; a = 2; out = a`, nil, 2) - expect(t, `a := 1; func() { a = 2 }(); out = a`, nil, 2) - expect(t, `a := 1; func() { a := 2 }(); out = a`, nil, 1) // "a := 2" defines a new local variable 'a' - expect(t, `a := 1; func() { b := 2; out = b }()`, nil, 2) - expect(t, ` -out = func() { - a := 2 - func() { - a = 3 // captured from outer scope - }() - return a -}() -`, nil, 3) - - expect(t, ` -func() { - a := 5 - out = func() { - a := 4 - return a - }() -}()`, nil, 4) - - expectError(t, `a := 1; a := 2`, nil, "redeclared") // redeclared in the same scope - expectError(t, `func() { a := 1; a := 2 }()`, nil, "redeclared") // redeclared in the same scope - - expect(t, `a := 1; a += 2; out = a`, nil, 3) - expect(t, `a := 1; a += 4 - 2;; out = a`, nil, 3) - expect(t, `a := 3; a -= 1;; out = a`, nil, 2) - expect(t, `a := 3; a -= 5 - 4;; out = a`, nil, 2) - expect(t, `a := 2; a *= 4;; out = a`, nil, 8) - expect(t, `a := 2; a *= 1 + 3;; out = a`, nil, 8) - expect(t, `a := 10; a /= 2;; out = a`, nil, 5) - expect(t, `a := 10; a /= 5 - 3;; out = a`, nil, 5) - - // compound assignment operator does not define new variable - expectError(t, `a += 4`, nil, "unresolved reference") - expectError(t, `a -= 4`, nil, "unresolved reference") - expectError(t, `a *= 4`, nil, "unresolved reference") - expectError(t, `a /= 4`, nil, "unresolved reference") - - expect(t, ` -f1 := func() { - f2 := func() { - a := 1 - a += 2 // it's a statement, not an expression - return a - }; - - return f2(); -}; - -out = f1();`, nil, 3) - expect(t, `f1 := func() { f2 := func() { a := 1; a += 4 - 2; return a }; return f2(); }; out = f1()`, nil, 3) - expect(t, `f1 := func() { f2 := func() { a := 3; a -= 1; return a }; return f2(); }; out = f1()`, nil, 2) - expect(t, `f1 := func() { f2 := func() { a := 3; a -= 5 - 4; return a }; return f2(); }; out = f1()`, nil, 2) - expect(t, `f1 := func() { f2 := func() { a := 2; a *= 4; return a }; return f2(); }; out = f1()`, nil, 8) - expect(t, `f1 := func() { f2 := func() { a := 2; a *= 1 + 3; return a }; return f2(); }; out = f1()`, nil, 8) - expect(t, `f1 := func() { f2 := func() { a := 10; a /= 2; return a }; return f2(); }; out = f1()`, nil, 5) - expect(t, `f1 := func() { f2 := func() { a := 10; a /= 5 - 3; return a }; return f2(); }; out = f1()`, nil, 5) - - expect(t, `a := 1; f1 := func() { f2 := func() { a += 2; return a }; return f2(); }; out = f1()`, nil, 3) - - expect(t, ` - f1 := func(a) { - return func(b) { - c := a - c += b * 2 - return c - } - } - - out = f1(3)(4) - `, nil, 11) - - expect(t, ` - out = func() { - a := 1 - func() { - a = 2 - func() { - a = 3 - func() { - a := 4 // declared new - }() - }() - }() - return a - }() - `, nil, 3) - - // write on free variables - expect(t, ` - f1 := func() { - a := 5 - - return func() { - a += 3 - return a - }() - } - out = f1() - `, nil, 8) - - expect(t, ` - out = func() { - f1 := func() { - a := 5 - add1 := func() { a += 1 } - add2 := func() { a += 2 } - a += 3 - return func() { a += 4; add1(); add2(); a += 5; return a } - } - return f1() - }()() - `, nil, 20) - - expect(t, ` - it := func(seq, fn) { - fn(seq[0]) - fn(seq[1]) - fn(seq[2]) - } - - foo := func(a) { - b := 0 - it([1, 2, 3], func(x) { - b = x + a - }) - return b - } - - out = foo(2) - `, nil, 5) - - expect(t, ` - it := func(seq, fn) { - fn(seq[0]) - fn(seq[1]) - fn(seq[2]) - } - - foo := func(a) { - b := 0 - it([1, 2, 3], func(x) { - b += x + a - }) - return b - } - - out = foo(2) - `, nil, 12) - - expect(t, ` -out = func() { - a := 1 - func() { - a = 2 - }() - return a -}() -`, nil, 2) - - expect(t, ` -f := func() { - a := 1 - return { - b: func() { a += 3 }, - c: func() { a += 2 }, - d: func() { return a } - } -} -m := f() -m.b() -m.c() -out = m.d() -`, nil, 6) - - expect(t, ` -each := func(s, x) { for i:=0; i> 2`, nil, 4) - - expect(t, `out = 1; out &= 1`, nil, 1) - expect(t, `out = 1; out |= 0`, nil, 1) - expect(t, `out = 1; out ^= 0`, nil, 1) - expect(t, `out = 1; out &^= 0`, nil, 1) - expect(t, `out = 1; out <<= 2`, nil, 4) - expect(t, `out = 16; out >>= 2`, nil, 4) - - expect(t, `out = ^0`, nil, ^0) - expect(t, `out = ^1`, nil, ^1) - expect(t, `out = ^55`, nil, ^55) - expect(t, `out = ^-55`, nil, ^-55) -} diff --git a/runtime/vm_boolean_test.go b/runtime/vm_boolean_test.go deleted file mode 100644 index eafcbb8..0000000 --- a/runtime/vm_boolean_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package runtime_test - -import ( - "testing" -) - -func TestBoolean(t *testing.T) { - expect(t, `out = true`, nil, true) - expect(t, `out = false`, nil, false) - - expect(t, `out = 1 < 2`, nil, true) - expect(t, `out = 1 > 2`, nil, false) - expect(t, `out = 1 < 1`, nil, false) - expect(t, `out = 1 > 2`, nil, false) - expect(t, `out = 1 == 1`, nil, true) - expect(t, `out = 1 != 1`, nil, false) - expect(t, `out = 1 == 2`, nil, false) - expect(t, `out = 1 != 2`, nil, true) - expect(t, `out = 1 <= 2`, nil, true) - expect(t, `out = 1 >= 2`, nil, false) - expect(t, `out = 1 <= 1`, nil, true) - expect(t, `out = 1 >= 2`, nil, false) - - expect(t, `out = true == true`, nil, true) - expect(t, `out = false == false`, nil, true) - expect(t, `out = true == false`, nil, false) - expect(t, `out = true != false`, nil, true) - expect(t, `out = false != true`, nil, true) - expect(t, `out = (1 < 2) == true`, nil, true) - expect(t, `out = (1 < 2) == false`, nil, false) - expect(t, `out = (1 > 2) == true`, nil, false) - expect(t, `out = (1 > 2) == false`, nil, true) - - expectError(t, `5 + true`, nil, "invalid operation") - expectError(t, `5 + true; 5`, nil, "invalid operation") - expectError(t, `-true`, nil, "invalid operation") - expectError(t, `true + false`, nil, "invalid operation") - expectError(t, `5; true + false; 5`, nil, "invalid operation") - expectError(t, `if (10 > 1) { true + false; }`, nil, "invalid operation") - expectError(t, ` -func() { - if (10 > 1) { - if (10 > 1) { - return true + false; - } - - return 1; - } -}() -`, nil, "invalid operation") - expectError(t, `if (true + false) { 10 }`, nil, "invalid operation") - expectError(t, `10 + (true + false)`, nil, "invalid operation") - expectError(t, `(true + false) + 20`, nil, "invalid operation") - expectError(t, `!(true + false)`, nil, "invalid operation") -} diff --git a/runtime/vm_builtin_test.go b/runtime/vm_builtin_test.go deleted file mode 100644 index 8a72779..0000000 --- a/runtime/vm_builtin_test.go +++ /dev/null @@ -1,186 +0,0 @@ -package runtime_test - -import ( - "testing" - - "github.com/d5/tengo" - "github.com/d5/tengo/objects" -) - -func TestBuiltinFunction(t *testing.T) { - expect(t, `out = len("")`, nil, 0) - expect(t, `out = len("four")`, nil, 4) - expect(t, `out = len("hello world")`, nil, 11) - expect(t, `out = len([])`, nil, 0) - expect(t, `out = len([1, 2, 3])`, nil, 3) - expect(t, `out = len({})`, nil, 0) - expect(t, `out = len({a:1, b:2})`, nil, 2) - expect(t, `out = len(immutable([]))`, nil, 0) - expect(t, `out = len(immutable([1, 2, 3]))`, nil, 3) - expect(t, `out = len(immutable({}))`, nil, 0) - expect(t, `out = len(immutable({a:1, b:2}))`, nil, 2) - expectError(t, `len(1)`, nil, "invalid type for argument") - expectError(t, `len("one", "two")`, nil, "wrong number of arguments") - - expect(t, `out = copy(1)`, nil, 1) - expectError(t, `copy(1, 2)`, nil, "wrong number of arguments") - - expect(t, `out = append([1, 2, 3], 4)`, nil, ARR{1, 2, 3, 4}) - expect(t, `out = append([1, 2, 3], 4, 5, 6)`, nil, ARR{1, 2, 3, 4, 5, 6}) - expect(t, `out = append([1, 2, 3], "foo", false)`, nil, ARR{1, 2, 3, "foo", false}) - - expect(t, `out = int(1)`, nil, 1) - expect(t, `out = int(1.8)`, nil, 1) - expect(t, `out = int("-522")`, nil, -522) - expect(t, `out = int(true)`, nil, 1) - expect(t, `out = int(false)`, nil, 0) - expect(t, `out = int('8')`, nil, 56) - expect(t, `out = int([1])`, nil, objects.UndefinedValue) - expect(t, `out = int({a: 1})`, nil, objects.UndefinedValue) - expect(t, `out = int(undefined)`, nil, objects.UndefinedValue) - expect(t, `out = int("-522", 1)`, nil, -522) - expect(t, `out = int(undefined, 1)`, nil, 1) - expect(t, `out = int(undefined, 1.8)`, nil, 1.8) - expect(t, `out = int(undefined, string(1))`, nil, "1") - expect(t, `out = int(undefined, undefined)`, nil, objects.UndefinedValue) - - expect(t, `out = string(1)`, nil, "1") - expect(t, `out = string(1.8)`, nil, "1.8") - expect(t, `out = string("-522")`, nil, "-522") - expect(t, `out = string(true)`, nil, "true") - expect(t, `out = string(false)`, nil, "false") - expect(t, `out = string('8')`, nil, "8") - expect(t, `out = string([1,8.1,true,3])`, nil, "[1, 8.1, true, 3]") - expect(t, `out = string({b: "foo"})`, nil, `{b: "foo"}`) - expect(t, `out = string(undefined)`, nil, objects.UndefinedValue) // not "undefined" - expect(t, `out = string(1, "-522")`, nil, "1") - expect(t, `out = string(undefined, "-522")`, nil, "-522") // not "undefined" - - expect(t, `out = float(1)`, nil, 1.0) - expect(t, `out = float(1.8)`, nil, 1.8) - expect(t, `out = float("-52.2")`, nil, -52.2) - expect(t, `out = float(true)`, nil, objects.UndefinedValue) - expect(t, `out = float(false)`, nil, objects.UndefinedValue) - expect(t, `out = float('8')`, nil, objects.UndefinedValue) - expect(t, `out = float([1,8.1,true,3])`, nil, objects.UndefinedValue) - expect(t, `out = float({a: 1, b: "foo"})`, nil, objects.UndefinedValue) - expect(t, `out = float(undefined)`, nil, objects.UndefinedValue) - expect(t, `out = float("-52.2", 1.8)`, nil, -52.2) - expect(t, `out = float(undefined, 1)`, nil, 1) - expect(t, `out = float(undefined, 1.8)`, nil, 1.8) - expect(t, `out = float(undefined, "-52.2")`, nil, "-52.2") - expect(t, `out = float(undefined, char(56))`, nil, '8') - expect(t, `out = float(undefined, undefined)`, nil, objects.UndefinedValue) - - expect(t, `out = char(56)`, nil, '8') - expect(t, `out = char(1.8)`, nil, objects.UndefinedValue) - expect(t, `out = char("-52.2")`, nil, objects.UndefinedValue) - expect(t, `out = char(true)`, nil, objects.UndefinedValue) - expect(t, `out = char(false)`, nil, objects.UndefinedValue) - expect(t, `out = char('8')`, nil, '8') - expect(t, `out = char([1,8.1,true,3])`, nil, objects.UndefinedValue) - expect(t, `out = char({a: 1, b: "foo"})`, nil, objects.UndefinedValue) - expect(t, `out = char(undefined)`, nil, objects.UndefinedValue) - expect(t, `out = char(56, 'a')`, nil, '8') - expect(t, `out = char(undefined, '8')`, nil, '8') - expect(t, `out = char(undefined, 56)`, nil, 56) - expect(t, `out = char(undefined, "-52.2")`, nil, "-52.2") - expect(t, `out = char(undefined, undefined)`, nil, objects.UndefinedValue) - - expect(t, `out = bool(1)`, nil, true) // non-zero integer: true - expect(t, `out = bool(0)`, nil, false) // zero: true - expect(t, `out = bool(1.8)`, nil, true) // all floats (except for NaN): true - expect(t, `out = bool(0.0)`, nil, true) // all floats (except for NaN): true - expect(t, `out = bool("false")`, nil, true) // non-empty string: true - expect(t, `out = bool("")`, nil, false) // empty string: false - expect(t, `out = bool(true)`, nil, true) // true: true - expect(t, `out = bool(false)`, nil, false) // false: false - expect(t, `out = bool('8')`, nil, true) // non-zero chars: true - expect(t, `out = bool(char(0))`, nil, false) // zero char: false - expect(t, `out = bool([1])`, nil, true) // non-empty arrays: true - expect(t, `out = bool([])`, nil, false) // empty array: false - expect(t, `out = bool({a: 1})`, nil, true) // non-empty maps: true - expect(t, `out = bool({})`, nil, false) // empty maps: false - expect(t, `out = bool(undefined)`, nil, false) // undefined: false - - expect(t, `out = bytes(1)`, nil, []byte{0}) - expect(t, `out = bytes(1.8)`, nil, objects.UndefinedValue) - expect(t, `out = bytes("-522")`, nil, []byte{'-', '5', '2', '2'}) - expect(t, `out = bytes(true)`, nil, objects.UndefinedValue) - expect(t, `out = bytes(false)`, nil, objects.UndefinedValue) - expect(t, `out = bytes('8')`, nil, objects.UndefinedValue) - expect(t, `out = bytes([1])`, nil, objects.UndefinedValue) - expect(t, `out = bytes({a: 1})`, nil, objects.UndefinedValue) - expect(t, `out = bytes(undefined)`, nil, objects.UndefinedValue) - expect(t, `out = bytes("-522", ['8'])`, nil, []byte{'-', '5', '2', '2'}) - expect(t, `out = bytes(undefined, "-522")`, nil, "-522") - expect(t, `out = bytes(undefined, 1)`, nil, 1) - expect(t, `out = bytes(undefined, 1.8)`, nil, 1.8) - expect(t, `out = bytes(undefined, int("-522"))`, nil, -522) - expect(t, `out = bytes(undefined, undefined)`, nil, objects.UndefinedValue) - - expect(t, `out = is_error(error(1))`, nil, true) - expect(t, `out = is_error(1)`, nil, false) - - expect(t, `out = is_undefined(undefined)`, nil, true) - expect(t, `out = is_undefined(error(1))`, nil, false) - - // type_name - expect(t, `out = type_name(1)`, nil, "int") - expect(t, `out = type_name(1.1)`, nil, "float") - expect(t, `out = type_name("a")`, nil, "string") - expect(t, `out = type_name([1,2,3])`, nil, "array") - expect(t, `out = type_name({k:1})`, nil, "map") - expect(t, `out = type_name('a')`, nil, "char") - expect(t, `out = type_name(true)`, nil, "bool") - expect(t, `out = type_name(false)`, nil, "bool") - expect(t, `out = type_name(bytes( 1))`, nil, "bytes") - expect(t, `out = type_name(undefined)`, nil, "undefined") - expect(t, `out = type_name(error("err"))`, nil, "error") - expect(t, `out = type_name(func() {})`, nil, "compiled-function") - expect(t, `a := func(x) { return func() { return x } }; out = type_name(a(5))`, nil, "closure") // closure - - // is_function - expect(t, `out = is_function(1)`, nil, false) - expect(t, `out = is_function(func() {})`, nil, true) - expect(t, `out = is_function(func(x) { return x })`, nil, true) - expect(t, `out = is_function(len)`, nil, false) // builtin function - expect(t, `a := func(x) { return func() { return x } }; out = is_function(a)`, nil, true) // function - expect(t, `a := func(x) { return func() { return x } }; out = is_function(a(5))`, nil, true) // closure - expect(t, `out = is_function(x)`, Opts().Symbol("x", &StringArray{Value: []string{"foo", "bar"}}).Skip2ndPass(), false) // user object - - // is_callable - expect(t, `out = is_callable(1)`, nil, false) - expect(t, `out = is_callable(func() {})`, nil, true) - expect(t, `out = is_callable(func(x) { return x })`, nil, true) - expect(t, `out = is_callable(len)`, nil, true) // builtin function - expect(t, `a := func(x) { return func() { return x } }; out = is_callable(a)`, nil, true) // function - expect(t, `a := func(x) { return func() { return x } }; out = is_callable(a(5))`, nil, true) // closure - expect(t, `out = is_callable(x)`, Opts().Symbol("x", &StringArray{Value: []string{"foo", "bar"}}).Skip2ndPass(), true) // user object - - expect(t, `out = format("")`, nil, "") - expect(t, `out = format("foo")`, nil, "foo") - expect(t, `out = format("foo %d %v %s", 1, 2, "bar")`, nil, "foo 1 2 bar") - expect(t, `out = format("foo %v", [1, "bar", true])`, nil, `foo [1, "bar", true]`) - expect(t, `out = format("foo %v %d", [1, "bar", true], 19)`, nil, `foo [1, "bar", true] 19`) - expect(t, `out = format("foo %v", {"a": {"b": {"c": [1, 2, 3]}}})`, nil, `foo {a: {b: {c: [1, 2, 3]}}}`) - expect(t, `out = format("%v", [1, [2, [3, 4]]])`, nil, `[1, [2, [3, 4]]]`) - - tengo.MaxStringLen = 9 - expectError(t, `format("%s", "1234567890")`, nil, "exceeding string size limit") - tengo.MaxStringLen = 2147483647 -} - -func TestBytesN(t *testing.T) { - curMaxBytesLen := tengo.MaxBytesLen - defer func() { tengo.MaxBytesLen = curMaxBytesLen }() - tengo.MaxBytesLen = 10 - - expect(t, `out = bytes(0)`, nil, make([]byte, 0)) - expect(t, `out = bytes(10)`, nil, make([]byte, 10)) - expectError(t, `bytes(11)`, nil, "bytes size limit") - - tengo.MaxBytesLen = 1000 - expect(t, `out = bytes(1000)`, nil, make([]byte, 1000)) - expectError(t, `bytes(1001)`, nil, "bytes size limit") -} diff --git a/runtime/vm_bytes_test.go b/runtime/vm_bytes_test.go deleted file mode 100644 index a17a395..0000000 --- a/runtime/vm_bytes_test.go +++ /dev/null @@ -1,18 +0,0 @@ -package runtime_test - -import ( - "testing" - - "github.com/d5/tengo/objects" -) - -func TestBytes(t *testing.T) { - expect(t, `out = bytes("Hello World!")`, nil, []byte("Hello World!")) - expect(t, `out = bytes("Hello") + bytes(" ") + bytes("World!")`, nil, []byte("Hello World!")) - - // bytes[] -> int - expect(t, `out = bytes("abcde")[0]`, nil, 97) - expect(t, `out = bytes("abcde")[1]`, nil, 98) - expect(t, `out = bytes("abcde")[4]`, nil, 101) - expect(t, `out = bytes("abcde")[10]`, nil, objects.UndefinedValue) -} diff --git a/runtime/vm_call_test.go b/runtime/vm_call_test.go deleted file mode 100644 index 81618c5..0000000 --- a/runtime/vm_call_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package runtime_test - -import "testing" - -func TestCall(t *testing.T) { - expect(t, `a := { b: func(x) { return x + 2 } }; out = a.b(5)`, nil, 7) - expect(t, `a := { b: { c: func(x) { return x + 2 } } }; out = a.b.c(5)`, nil, 7) - expect(t, `a := { b: { c: func(x) { return x + 2 } } }; out = a["b"].c(5)`, nil, 7) - expectError(t, `a := 1 -b := func(a, c) { - c(a) -} - -c := func(a) { - a() -} -b(a, c) -`, nil, "Runtime Error: not callable: int\n\tat test:7:4\n\tat test:3:4\n\tat test:9:1") -} diff --git a/runtime/vm_char_test.go b/runtime/vm_char_test.go deleted file mode 100644 index 42211bf..0000000 --- a/runtime/vm_char_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package runtime_test - -import "testing" - -func TestChar(t *testing.T) { - expect(t, `out = 'a'`, nil, 'a') - expect(t, `out = '九'`, nil, rune(20061)) - expect(t, `out = 'Æ'`, nil, rune(198)) - - expect(t, `out = '0' + '9'`, nil, rune(105)) - expect(t, `out = '0' + 9`, nil, '9') - expect(t, `out = '9' - 4`, nil, '5') - expect(t, `out = '0' == '0'`, nil, true) - expect(t, `out = '0' != '0'`, nil, false) - expect(t, `out = '2' < '4'`, nil, true) - expect(t, `out = '2' > '4'`, nil, false) - expect(t, `out = '2' <= '4'`, nil, true) - expect(t, `out = '2' >= '4'`, nil, false) - expect(t, `out = '4' < '4'`, nil, false) - expect(t, `out = '4' > '4'`, nil, false) - expect(t, `out = '4' <= '4'`, nil, true) - expect(t, `out = '4' >= '4'`, nil, true) -} diff --git a/runtime/vm_cond_test.go b/runtime/vm_cond_test.go deleted file mode 100644 index 56326ff..0000000 --- a/runtime/vm_cond_test.go +++ /dev/null @@ -1,45 +0,0 @@ -package runtime_test - -import "testing" - -func TestCondExpr(t *testing.T) { - expect(t, `out = true ? 5 : 10`, nil, 5) - expect(t, `out = false ? 5 : 10`, nil, 10) - expect(t, `out = (1 == 1) ? 2 + 3 : 12 - 2`, nil, 5) - expect(t, `out = (1 != 1) ? 2 + 3 : 12 - 2`, nil, 10) - expect(t, `out = (1 == 1) ? true ? 10 - 8 : 1 + 3 : 12 - 2`, nil, 2) - expect(t, `out = (1 == 1) ? false ? 10 - 8 : 1 + 3 : 12 - 2`, nil, 4) - - expect(t, ` -out = 0 -f1 := func() { out += 10 } -f2 := func() { out = -out } -true ? f1() : f2() -`, nil, 10) - expect(t, ` -out = 5 -f1 := func() { out += 10 } -f2 := func() { out = -out } -false ? f1() : f2() -`, nil, -5) - expect(t, ` -f1 := func(a) { return a + 2 } -f2 := func(a) { return a - 2 } -f3 := func(a) { return a + 10 } -f4 := func(a) { return -a } - -f := func(c) { - return c == 0 ? f1(c) : f2(c) ? f3(c) : f4(c) -} - -out = [f(0), f(1), f(2)] -`, nil, ARR{2, 11, -2}) - - expect(t, `f := func(a) { return -a }; out = f(true ? 5 : 3)`, nil, -5) - expect(t, `out = [false?5:10, true?1:2]`, nil, ARR{10, 1}) - - expect(t, ` -out = 1 > 2 ? - 1 + 2 + 3 : - 10 - 5`, nil, 5) -} diff --git a/runtime/vm_equality_test.go b/runtime/vm_equality_test.go deleted file mode 100644 index 0a3eacd..0000000 --- a/runtime/vm_equality_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package runtime_test - -import ( - "fmt" - "testing" -) - -func TestEquality(t *testing.T) { - testEquality(t, `1`, `1`, true) - testEquality(t, `1`, `2`, false) - - testEquality(t, `1.0`, `1.0`, true) - testEquality(t, `1.0`, `1.1`, false) - - testEquality(t, `true`, `true`, true) - testEquality(t, `true`, `false`, false) - - testEquality(t, `"foo"`, `"foo"`, true) - testEquality(t, `"foo"`, `"bar"`, false) - - testEquality(t, `'f'`, `'f'`, true) - testEquality(t, `'f'`, `'b'`, false) - - testEquality(t, `[]`, `[]`, true) - testEquality(t, `[1]`, `[1]`, true) - testEquality(t, `[1]`, `[1, 2]`, false) - testEquality(t, `["foo", "bar"]`, `["foo", "bar"]`, true) - testEquality(t, `["foo", "bar"]`, `["bar", "foo"]`, false) - - testEquality(t, `{}`, `{}`, true) - testEquality(t, `{a: 1, b: 2}`, `{b: 2, a: 1}`, true) - testEquality(t, `{a: 1, b: 2}`, `{b: 2}`, false) - testEquality(t, `{a: 1, b: {}}`, `{b: {}, a: 1}`, true) - - testEquality(t, `1`, `"foo"`, false) - testEquality(t, `1`, `true`, false) - testEquality(t, `[1]`, `["1"]`, false) - testEquality(t, `[1, [2]]`, `[1, ["2"]]`, false) - testEquality(t, `{a: 1}`, `{a: "1"}`, false) - testEquality(t, `{a: 1, b: {c: 2}}`, `{a: 1, b: {c: "2"}}`, false) -} - -func testEquality(t *testing.T, lhs, rhs string, expected bool) { - // 1. equality is commutative - // 2. equality and inequality must be always opposite - expect(t, fmt.Sprintf("out = %s == %s", lhs, rhs), nil, expected) - expect(t, fmt.Sprintf("out = %s == %s", rhs, lhs), nil, expected) - expect(t, fmt.Sprintf("out = %s != %s", lhs, rhs), nil, !expected) - expect(t, fmt.Sprintf("out = %s != %s", rhs, lhs), nil, !expected) -} diff --git a/runtime/vm_error_report_test.go b/runtime/vm_error_report_test.go deleted file mode 100644 index 72bb17a..0000000 --- a/runtime/vm_error_report_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package runtime_test - -import "testing" - -func TestVMErrorInfo(t *testing.T) { - expectError(t, `a := 5 -a + "boo"`, - nil, "Runtime Error: invalid operation: int + string\n\tat test:2:1") - - expectError(t, `a := 5 -b := a(5)`, - nil, "Runtime Error: not callable: int\n\tat test:2:6") - - expectError(t, `a := 5 -b := {} -b.x.y = 10`, - nil, "Runtime Error: not index-assignable: undefined\n\tat test:3:1") - - expectError(t, ` -a := func() { - b := 5 - b += "foo" -} -a()`, - nil, "Runtime Error: invalid operation: int + string\n\tat test:4:2") - - expectError(t, `a := 5 -a + import("mod1")`, Opts().Module( - "mod1", `export "foo"`, - ), ": invalid operation: int + string\n\tat test:2:1") - - expectError(t, `a := import("mod1")()`, - Opts().Module( - "mod1", ` -export func() { - b := 5 - return b + "foo" -}`), "Runtime Error: invalid operation: int + string\n\tat mod1:4:9") - - expectError(t, `a := import("mod1")()`, - Opts().Module( - "mod1", `export import("mod2")()`). - Module( - "mod2", ` -export func() { - b := 5 - return b + "foo" -}`), "Runtime Error: invalid operation: int + string\n\tat mod2:4:9") -} diff --git a/runtime/vm_error_test.go b/runtime/vm_error_test.go deleted file mode 100644 index 8cefda1..0000000 --- a/runtime/vm_error_test.go +++ /dev/null @@ -1,21 +0,0 @@ -package runtime_test - -import ( - "testing" -) - -func TestError(t *testing.T) { - expect(t, `out = error(1)`, nil, errorObject(1)) - expect(t, `out = error(1).value`, nil, 1) - expect(t, `out = error("some error")`, nil, errorObject("some error")) - expect(t, `out = error("some" + " error")`, nil, errorObject("some error")) - expect(t, `out = func() { return error(5) }()`, nil, errorObject(5)) - expect(t, `out = error(error("foo"))`, nil, errorObject(errorObject("foo"))) - expect(t, `out = error("some error")`, nil, errorObject("some error")) - expect(t, `out = error("some error").value`, nil, "some error") - expect(t, `out = error("some error")["value"]`, nil, "some error") - - expectError(t, `error("error").err`, nil, "invalid index on error") - expectError(t, `error("error").value_`, nil, "invalid index on error") - expectError(t, `error([1,2,3])[1]`, nil, "invalid index on error") -} diff --git a/runtime/vm_float_test.go b/runtime/vm_float_test.go deleted file mode 100644 index 0eb2f84..0000000 --- a/runtime/vm_float_test.go +++ /dev/null @@ -1,15 +0,0 @@ -package runtime_test - -import ( - "testing" -) - -func TestFloat(t *testing.T) { - expect(t, `out = 0.0`, nil, 0.0) - expect(t, `out = -10.3`, nil, -10.3) - expect(t, `out = 3.2 + 2.0 * -4.0`, nil, -4.8) - expect(t, `out = 4 + 2.3`, nil, 6.3) - expect(t, `out = 2.3 + 4`, nil, 6.3) - expect(t, `out = +5.0`, nil, 5.0) - expect(t, `out = -5.0 + +5.0`, nil, 0.0) -} diff --git a/runtime/vm_for_in_test.go b/runtime/vm_for_in_test.go deleted file mode 100644 index 21904ef..0000000 --- a/runtime/vm_for_in_test.go +++ /dev/null @@ -1,25 +0,0 @@ -package runtime_test - -import ( - "testing" -) - -func TestForIn(t *testing.T) { - // array - expect(t, `out = 0; for x in [1, 2, 3] { out += x }`, nil, 6) // value - expect(t, `out = 0; for i, x in [1, 2, 3] { out += i + x }`, nil, 9) // index, value - expect(t, `out = 0; func() { for i, x in [1, 2, 3] { out += i + x } }()`, nil, 9) // index, value - expect(t, `out = 0; for i, _ in [1, 2, 3] { out += i }`, nil, 3) // index, _ - expect(t, `out = 0; func() { for i, _ in [1, 2, 3] { out += i } }()`, nil, 3) // index, _ - - // map - expect(t, `out = 0; for v in {a:2,b:3,c:4} { out += v }`, nil, 9) // value - expect(t, `out = ""; for k, v in {a:2,b:3,c:4} { out = k; if v==3 { break } }`, nil, "b") // key, value - expect(t, `out = ""; for k, _ in {a:2} { out += k }`, nil, "a") // key, _ - expect(t, `out = 0; for _, v in {a:2,b:3,c:4} { out += v }`, nil, 9) // _, value - expect(t, `out = ""; func() { for k, v in {a:2,b:3,c:4} { out = k; if v==3 { break } } }()`, nil, "b") // key, value - - // string - expect(t, `out = ""; for c in "abcde" { out += c }`, nil, "abcde") - expect(t, `out = ""; for i, c in "abcde" { if i == 2 { continue }; out += c }`, nil, "abde") -} diff --git a/runtime/vm_for_test.go b/runtime/vm_for_test.go deleted file mode 100644 index 2dc492a..0000000 --- a/runtime/vm_for_test.go +++ /dev/null @@ -1,238 +0,0 @@ -package runtime_test - -import ( - "testing" -) - -func TestFor(t *testing.T) { - expect(t, ` - out = 0 - for { - out++ - if out == 5 { - break - } - }`, nil, 5) - - expect(t, ` - out = 0 - for { - out++ - if out == 5 { - break - } - }`, nil, 5) - - expect(t, ` - out = 0 - a := 0 - for { - a++ - if a == 3 { continue } - if a == 5 { break } - out += a - }`, nil, 7) // 1 + 2 + 4 - - expect(t, ` - out = 0 - a := 0 - for { - a++ - if a == 3 { continue } - out += a - if a == 5 { break } - }`, nil, 12) // 1 + 2 + 4 + 5 - - expect(t, ` - out = 0 - for true { - out++ - if out == 5 { - break - } - }`, nil, 5) - - expect(t, ` - a := 0 - for true { - a++ - if a == 5 { - break - } - } - out = a`, nil, 5) - - expect(t, ` - out = 0 - a := 0 - for true { - a++ - if a == 3 { continue } - if a == 5 { break } - out += a - }`, nil, 7) // 1 + 2 + 4 - - expect(t, ` - out = 0 - a := 0 - for true { - a++ - if a == 3 { continue } - out += a - if a == 5 { break } - }`, nil, 12) // 1 + 2 + 4 + 5 - - expect(t, ` - out = 0 - func() { - for true { - out++ - if out == 5 { - return - } - } - }()`, nil, 5) - - expect(t, ` - out = 0 - for a:=1; a<=10; a++ { - out += a - }`, nil, 55) - - expect(t, ` - out = 0 - for a:=1; a<=3; a++ { - for b:=3; b<=6; b++ { - out += b - } - }`, nil, 54) - - expect(t, ` - out = 0 - func() { - for { - out++ - if out == 5 { - break - } - } - }()`, nil, 5) - - expect(t, ` - out = 0 - func() { - for true { - out++ - if out == 5 { - break - } - } - }()`, nil, 5) - - expect(t, ` - out = func() { - a := 0 - for { - a++ - if a == 5 { - break - } - } - return a - }()`, nil, 5) - - expect(t, ` - out = func() { - a := 0 - for true { - a++ - if a== 5 { - break - } - } - return a - }()`, nil, 5) - - expect(t, ` - out = func() { - a := 0 - func() { - for { - a++ - if a == 5 { - break - } - } - }() - return a - }()`, nil, 5) - - expect(t, ` - out = func() { - a := 0 - func() { - for true { - a++ - if a == 5 { - break - } - } - }() - return a - }()`, nil, 5) - - expect(t, ` - out = func() { - sum := 0 - for a:=1; a<=10; a++ { - sum += a - } - return sum - }()`, nil, 55) - - expect(t, ` - out = func() { - sum := 0 - for a:=1; a<=4; a++ { - for b:=3; b<=5; b++ { - sum += b - } - } - return sum - }()`, nil, 48) // (3+4+5) * 4 - - expect(t, ` - a := 1 - for ; a<=10; a++ { - if a == 5 { - break - } - } - out = a`, nil, 5) - - expect(t, ` - out = 0 - for a:=1; a<=10; a++ { - if a == 3 { - continue - } - out += a - if a == 5 { - break - } - }`, nil, 12) // 1 + 2 + 4 + 5 - - expect(t, ` - out = 0 - for a:=1; a<=10; { - if a == 3 { - a++ - continue - } - out += a - if a == 5 { - break - } - a++ - }`, nil, 12) // 1 + 2 + 4 + 5 -} diff --git a/runtime/vm_function_test.go b/runtime/vm_function_test.go deleted file mode 100644 index 9a481cd..0000000 --- a/runtime/vm_function_test.go +++ /dev/null @@ -1,298 +0,0 @@ -package runtime_test - -import ( - "testing" - - "github.com/d5/tengo/objects" -) - -func TestFunction(t *testing.T) { - // function with no "return" statement returns "invalid" value. - expect(t, `f1 := func() {}; out = f1();`, nil, objects.UndefinedValue) - expect(t, `f1 := func() {}; f2 := func() { return f1(); }; f1(); out = f2();`, nil, objects.UndefinedValue) - expect(t, `f := func(x) { x; }; out = f(5);`, nil, objects.UndefinedValue) - - expect(t, `f := func(...x) { return x; }; out = f(1,2,3);`, nil, ARR{1, 2, 3}) - - expect(t, `f := func(a, b, ...x) { return [a, b, x]; }; out = f(8,9,1,2,3);`, nil, ARR{8, 9, ARR{1, 2, 3}}) - - expect(t, `f := func(v) { x := 2; return func(a, ...b){ return [a, b, v+x]}; }; out = f(5)("a", "b");`, nil, - ARR{"a", ARR{"b"}, 7}) - - expect(t, `f := func(...x) { return x; }; out = f();`, nil, &objects.Array{Value: []objects.Object{}}) - - expect(t, `f := func(a, b, ...x) { return [a, b, x]; }; out = f(8, 9);`, nil, - ARR{8, 9, ARR{}}) - - expect(t, `f := func(v) { x := 2; return func(a, ...b){ return [a, b, v+x]}; }; out = f(5)("a");`, nil, - ARR{"a", ARR{}, 7}) - - expectError(t, `f := func(a, b, ...x) { return [a, b, x]; }; f();`, nil, - "Runtime Error: wrong number of arguments: want>=2, got=0\n\tat test:1:46") - - expectError(t, `f := func(a, b, ...x) { return [a, b, x]; }; f(1);`, nil, - "Runtime Error: wrong number of arguments: want>=2, got=1\n\tat test:1:46") - - expect(t, `f := func(x) { return x; }; out = f(5);`, nil, 5) - expect(t, `f := func(x) { return x * 2; }; out = f(5);`, nil, 10) - expect(t, `f := func(x, y) { return x + y; }; out = f(5, 5);`, nil, 10) - expect(t, `f := func(x, y) { return x + y; }; out = f(5 + 5, f(5, 5));`, nil, 20) - expect(t, `out = func(x) { return x; }(5)`, nil, 5) - expect(t, `x := 10; f := func(x) { return x; }; f(5); out = x;`, nil, 10) - - expect(t, ` - f2 := func(a) { - f1 := func(a) { - return a * 2; - }; - - return f1(a) * 3; - }; - - out = f2(10); - `, nil, 60) - - expect(t, ` - f1 := func(f) { - a := [undefined] - a[0] = func() { return f(a) } - return a[0]() - } - - out = f1(func(a) { return 2 }) - `, nil, 2) - - // closures - expect(t, ` - newAdder := func(x) { - return func(y) { return x + y }; - }; - - add2 := newAdder(2); - out = add2(5); - `, nil, 7) - - // function as a argument - expect(t, ` - add := func(a, b) { return a + b }; - sub := func(a, b) { return a - b }; - applyFunc := func(a, b, f) { return f(a, b) }; - - out = applyFunc(applyFunc(2, 2, add), 3, sub); - `, nil, 1) - - expect(t, `f1 := func() { return 5 + 10; }; out = f1();`, nil, 15) - expect(t, `f1 := func() { return 1 }; f2 := func() { return 2 }; out = f1() + f2()`, nil, 3) - expect(t, `f1 := func() { return 1 }; f2 := func() { return f1() + 2 }; f3 := func() { return f2() + 3 }; out = f3()`, nil, 6) - expect(t, `f1 := func() { return 99; 100 }; out = f1();`, nil, 99) - expect(t, `f1 := func() { return 99; return 100 }; out = f1();`, nil, 99) - expect(t, `f1 := func() { return 33; }; f2 := func() { return f1 }; out = f2()();`, nil, 33) - expect(t, `one := func() { one = 1; return one }; out = one()`, nil, 1) - expect(t, `three := func() { one := 1; two := 2; return one + two }; out = three()`, nil, 3) - expect(t, `three := func() { one := 1; two := 2; return one + two }; seven := func() { three := 3; four := 4; return three + four }; out = three() + seven()`, nil, 10) - expect(t, ` - foo1 := func() { - foo := 50 - return foo - } - foo2 := func() { - foo := 100 - return foo - } - out = foo1() + foo2()`, nil, 150) - expect(t, ` - g := 50; - minusOne := func() { - n := 1; - return g - n; - }; - minusTwo := func() { - n := 2; - return g - n; - }; - out = minusOne() + minusTwo() - `, nil, 97) - expect(t, ` - f1 := func() { - f2 := func() { return 1; } - return f2 - }; - out = f1()() - `, nil, 1) - - expect(t, ` - f1 := func(a) { return a; }; - out = f1(4)`, nil, 4) - expect(t, ` - f1 := func(a, b) { return a + b; }; - out = f1(1, 2)`, nil, 3) - - expect(t, ` - sum := func(a, b) { - c := a + b; - return c; - }; - out = sum(1, 2);`, nil, 3) - - expect(t, ` - sum := func(a, b) { - c := a + b; - return c; - }; - out = sum(1, 2) + sum(3, 4);`, nil, 10) - - expect(t, ` - sum := func(a, b) { - c := a + b - return c - }; - outer := func() { - return sum(1, 2) + sum(3, 4) - }; - out = outer();`, nil, 10) - - expect(t, ` - g := 10; - - sum := func(a, b) { - c := a + b; - return c + g; - } - - outer := func() { - return sum(1, 2) + sum(3, 4) + g; - } - - out = outer() + g - `, nil, 50) - - expectError(t, `func() { return 1; }(1)`, nil, "wrong number of arguments") - expectError(t, `func(a) { return a; }()`, nil, "wrong number of arguments") - expectError(t, `func(a, b) { return a + b; }(1)`, nil, "wrong number of arguments") - - expect(t, ` - f1 := func(a) { - return func() { return a; }; - }; - f2 := f1(99); - out = f2() - `, nil, 99) - - expect(t, ` - f1 := func(a, b) { - return func(c) { return a + b + c }; - }; - - f2 := f1(1, 2); - out = f2(8); - `, nil, 11) - expect(t, ` - f1 := func(a, b) { - c := a + b; - return func(d) { return c + d }; - }; - f2 := f1(1, 2); - out = f2(8); - `, nil, 11) - expect(t, ` - f1 := func(a, b) { - c := a + b; - return func(d) { - e := d + c; - return func(f) { return e + f }; - } - }; - f2 := f1(1, 2); - f3 := f2(3); - out = f3(8); - `, nil, 14) - expect(t, ` - a := 1; - f1 := func(b) { - return func(c) { - return func(d) { return a + b + c + d } - }; - }; - f2 := f1(2); - f3 := f2(3); - out = f3(8); - `, nil, 14) - expect(t, ` - f1 := func(a, b) { - one := func() { return a; }; - two := func() { return b; }; - return func() { return one() + two(); } - }; - f2 := f1(9, 90); - out = f2(); - `, nil, 99) - - // global function recursion - expect(t, ` - fib := func(x) { - if x == 0 { - return 0 - } else if x == 1 { - return 1 - } else { - return fib(x-1) + fib(x-2) - } - } - out = fib(15)`, nil, 610) - - // local function recursion - expect(t, ` -out = func() { - sum := func(x) { - return x == 0 ? 0 : x + sum(x-1) - } - return sum(5) -}()`, nil, 15) - - expectError(t, `return 5`, nil, "return not allowed outside function") - - // closure and block scopes - expect(t, ` -func() { - a := 10 - func() { - b := 5 - if true { - out = a + 5 - } - }() -}()`, nil, 15) - expect(t, ` -func() { - a := 10 - b := func() { return 5 } - func() { - if b() { - out = a + b() - } - }() -}()`, nil, 15) - expect(t, ` -func() { - a := 10 - func() { - b := func() { return 5 } - func() { - if true { - out = a + b() - } - }() - }() -}()`, nil, 15) - - // function skipping return - expect(t, `out = func() {}()`, nil, objects.UndefinedValue) - expect(t, `out = func(v) { if v { return true } }(1)`, nil, true) - expect(t, `out = func(v) { if v { return true } }(0)`, nil, objects.UndefinedValue) - expect(t, `out = func(v) { if v { } else { return true } }(1)`, nil, objects.UndefinedValue) - expect(t, `out = func(v) { if v { return } }(1)`, nil, objects.UndefinedValue) - expect(t, `out = func(v) { if v { return } }(0)`, nil, objects.UndefinedValue) - expect(t, `out = func(v) { if v { } else { return } }(1)`, nil, objects.UndefinedValue) - expect(t, `out = func(v) { for ;;v++ { if v == 3 { return true } } }(1)`, nil, true) - expect(t, `out = func(v) { for ;;v++ { if v == 3 { break } } }(1)`, nil, objects.UndefinedValue) -} diff --git a/runtime/vm_if_test.go b/runtime/vm_if_test.go deleted file mode 100644 index 51f685b..0000000 --- a/runtime/vm_if_test.go +++ /dev/null @@ -1,103 +0,0 @@ -package runtime_test - -import ( - "testing" - - "github.com/d5/tengo/objects" -) - -func TestIf(t *testing.T) { - expect(t, `if (true) { out = 10 }`, nil, 10) - expect(t, `if (false) { out = 10 }`, nil, objects.UndefinedValue) - expect(t, `if (false) { out = 10 } else { out = 20 }`, nil, 20) - expect(t, `if (1) { out = 10 }`, nil, 10) - expect(t, `if (0) { out = 10 } else { out = 20 }`, nil, 20) - expect(t, `if (1 < 2) { out = 10 }`, nil, 10) - expect(t, `if (1 > 2) { out = 10 }`, nil, objects.UndefinedValue) - expect(t, `if (1 < 2) { out = 10 } else { out = 20 }`, nil, 10) - expect(t, `if (1 > 2) { out = 10 } else { out = 20 }`, nil, 20) - - expect(t, `if (1 < 2) { out = 10 } else if (1 > 2) { out = 20 } else { out = 30 }`, nil, 10) - expect(t, `if (1 > 2) { out = 10 } else if (1 < 2) { out = 20 } else { out = 30 }`, nil, 20) - expect(t, `if (1 > 2) { out = 10 } else if (1 == 2) { out = 20 } else { out = 30 }`, nil, 30) - expect(t, `if (1 > 2) { out = 10 } else if (1 == 2) { out = 20 } else if (1 < 2) { out = 30 } else { out = 40 }`, nil, 30) - expect(t, `if (1 > 2) { out = 10 } else if (1 < 2) { out = 20; out = 21; out = 22 } else { out = 30 }`, nil, 22) - expect(t, `if (1 > 2) { out = 10 } else if (1 == 2) { out = 20 } else { out = 30; out = 31; out = 32}`, nil, 32) - expect(t, `if (1 > 2) { out = 10 } else if (1 < 2) { if (1 == 2) { out = 21 } else { out = 22 } } else { out = 30 }`, nil, 22) - expect(t, `if (1 > 2) { out = 10 } else if (1 < 2) { if (1 == 2) { out = 21 } else if (2 == 3) { out = 22 } else { out = 23 } } else { out = 30 }`, nil, 23) - expect(t, `if (1 > 2) { out = 10 } else if (1 == 2) { if (1 == 2) { out = 21 } else if (2 == 3) { out = 22 } else { out = 23 } } else { out = 30 }`, nil, 30) - expect(t, `if (1 > 2) { out = 10 } else if (1 == 2) { out = 20 } else { if (1 == 2) { out = 31 } else if (2 == 3) { out = 32 } else { out = 33 } }`, nil, 33) - - expect(t, `if a:=0; a<1 { out = 10 }`, nil, 10) - expect(t, `a:=0; if a++; a==1 { out = 10 }`, nil, 10) - expect(t, ` -func() { - a := 1 - if a++; a > 1 { - out = a - } -}() -`, nil, 2) - expect(t, ` -func() { - a := 1 - if a++; a == 1 { - out = 10 - } else { - out = 20 - } -}() -`, nil, 20) - expect(t, ` -func() { - a := 1 - - func() { - if a++; a > 1 { - a++ - } - }() - - out = a -}() -`, nil, 3) - - // expression statement in init (should not leave objects on stack) - expect(t, `a := 1; if a; a { out = a }`, nil, 1) - expect(t, `a := 1; if a + 4; a { out = a }`, nil, 1) - - // dead code elimination - expect(t, ` -out = func() { - if false { return 1 } - - a := undefined - - a = 2 - if !a { - b := func() { - return is_callable(a) ? a(8) : a - }() - if is_error(b) { - return b - } else if !is_undefined(b) { - return immutable(b) - } - } - - a = 3 - if a { - b := func() { - return is_callable(a) ? a(9) : a - }() - if is_error(b) { - return b - } else if !is_undefined(b) { - return immutable(b) - } - } - - return a -}() -`, nil, 3) -} diff --git a/runtime/vm_immutable_test.go b/runtime/vm_immutable_test.go deleted file mode 100644 index 99ffd5b..0000000 --- a/runtime/vm_immutable_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package runtime_test - -import ( - "testing" - - "github.com/d5/tengo/objects" -) - -func TestImmutable(t *testing.T) { - // primitive types are already immutable values - // immutable expression has no effects. - expect(t, `a := immutable(1); out = a`, nil, 1) - expect(t, `a := 5; b := immutable(a); out = b`, nil, 5) - expect(t, `a := immutable(1); a = 5; out = a`, nil, 5) - - // array - expectError(t, `a := immutable([1, 2, 3]); a[1] = 5`, nil, "not index-assignable") - expectError(t, `a := immutable(["foo", [1,2,3]]); a[1] = "bar"`, nil, "not index-assignable") - expect(t, `a := immutable(["foo", [1,2,3]]); a[1][1] = "bar"; out = a`, nil, IARR{"foo", ARR{1, "bar", 3}}) - expectError(t, `a := immutable(["foo", immutable([1,2,3])]); a[1][1] = "bar"`, nil, "not index-assignable") - expectError(t, `a := ["foo", immutable([1,2,3])]; a[1][1] = "bar"`, nil, "not index-assignable") - expect(t, `a := immutable([1,2,3]); b := copy(a); b[1] = 5; out = b`, nil, ARR{1, 5, 3}) - expect(t, `a := immutable([1,2,3]); b := copy(a); b[1] = 5; out = a`, nil, IARR{1, 2, 3}) - expect(t, `out = immutable([1,2,3]) == [1,2,3]`, nil, true) - expect(t, `out = immutable([1,2,3]) == immutable([1,2,3])`, nil, true) - expect(t, `out = [1,2,3] == immutable([1,2,3])`, nil, true) - expect(t, `out = immutable([1,2,3]) == [1,2]`, nil, false) - expect(t, `out = immutable([1,2,3]) == immutable([1,2])`, nil, false) - expect(t, `out = [1,2,3] == immutable([1,2])`, nil, false) - expect(t, `out = immutable([1, 2, 3, 4])[1]`, nil, 2) - expect(t, `out = immutable([1, 2, 3, 4])[1:3]`, nil, ARR{2, 3}) - expect(t, `a := immutable([1,2,3]); a = 5; out = a`, nil, 5) - expect(t, `a := immutable([1, 2, 3]); out = a[5]`, nil, objects.UndefinedValue) - - // map - expectError(t, `a := immutable({b: 1, c: 2}); a.b = 5`, nil, "not index-assignable") - expectError(t, `a := immutable({b: 1, c: 2}); a["b"] = "bar"`, nil, "not index-assignable") - expect(t, `a := immutable({b: 1, c: [1,2,3]}); a.c[1] = "bar"; out = a`, nil, IMAP{"b": 1, "c": ARR{1, "bar", 3}}) - expectError(t, `a := immutable({b: 1, c: immutable([1,2,3])}); a.c[1] = "bar"`, nil, "not index-assignable") - expectError(t, `a := {b: 1, c: immutable([1,2,3])}; a.c[1] = "bar"`, nil, "not index-assignable") - expect(t, `out = immutable({a:1,b:2}) == {a:1,b:2}`, nil, true) - expect(t, `out = immutable({a:1,b:2}) == immutable({a:1,b:2})`, nil, true) - expect(t, `out = {a:1,b:2} == immutable({a:1,b:2})`, nil, true) - expect(t, `out = immutable({a:1,b:2}) == {a:1,b:3}`, nil, false) - expect(t, `out = immutable({a:1,b:2}) == immutable({a:1,b:3})`, nil, false) - expect(t, `out = {a:1,b:2} == immutable({a:1,b:3})`, nil, false) - expect(t, `out = immutable({a:1,b:2}).b`, nil, 2) - expect(t, `out = immutable({a:1,b:2})["b"]`, nil, 2) - expect(t, `a := immutable({a:1,b:2}); a = 5; out = 5`, nil, 5) - expect(t, `a := immutable({a:1,b:2}); out = a.c`, nil, objects.UndefinedValue) - - expect(t, `a := immutable({b: 5, c: "foo"}); out = a.b`, nil, 5) - expectError(t, `a := immutable({b: 5, c: "foo"}); a.b = 10`, nil, "not index-assignable") -} diff --git a/runtime/vm_inc_dec_test.go b/runtime/vm_inc_dec_test.go deleted file mode 100644 index 0845e6f..0000000 --- a/runtime/vm_inc_dec_test.go +++ /dev/null @@ -1,21 +0,0 @@ -package runtime_test - -import ( - "testing" -) - -func TestIncDec(t *testing.T) { - expect(t, `out = 0; out++`, nil, 1) - expect(t, `out = 0; out--`, nil, -1) - expect(t, `a := 0; a++; out = a`, nil, 1) - expect(t, `a := 0; a++; a--; out = a`, nil, 0) - - // this seems strange but it works because 'a += b' is - // translated into 'a = a + b' and string type takes other types for + operator. - expect(t, `a := "foo"; a++; out = a`, nil, "foo1") - expectError(t, `a := "foo"; a--`, nil, "invalid operation") - - expectError(t, `a++`, nil, "unresolved reference") // not declared - expectError(t, `a--`, nil, "unresolved reference") // not declared - expectError(t, `4++`, nil, "unresolved reference") -} diff --git a/runtime/vm_indexable_test.go b/runtime/vm_indexable_test.go deleted file mode 100644 index 77b69eb..0000000 --- a/runtime/vm_indexable_test.go +++ /dev/null @@ -1,268 +0,0 @@ -package runtime_test - -import ( - "strings" - "testing" - - "github.com/d5/tengo/compiler/token" - "github.com/d5/tengo/objects" -) - -type objectImpl struct{} - -func (objectImpl) TypeName() string { return "" } -func (objectImpl) String() string { return "" } -func (objectImpl) IsFalsy() bool { return false } -func (objectImpl) Equals(another objects.Object) bool { return false } -func (objectImpl) Copy() objects.Object { return nil } -func (objectImpl) BinaryOp(token.Token, objects.Object) (objects.Object, error) { - return nil, objects.ErrInvalidOperator -} - -type StringDict struct { - objectImpl - Value map[string]string -} - -func (o *StringDict) TypeName() string { - return "string-dict" -} - -func (o *StringDict) IndexGet(index objects.Object) (objects.Object, error) { - strIdx, ok := index.(*objects.String) - if !ok { - return nil, objects.ErrInvalidIndexType - } - - for k, v := range o.Value { - if strings.ToLower(strIdx.Value) == strings.ToLower(k) { - return &objects.String{Value: v}, nil - } - } - - return objects.UndefinedValue, nil -} - -func (o *StringDict) IndexSet(index, value objects.Object) error { - strIdx, ok := index.(*objects.String) - if !ok { - return objects.ErrInvalidIndexType - } - - strVal, ok := objects.ToString(value) - if !ok { - return objects.ErrInvalidIndexValueType - } - - o.Value[strings.ToLower(strIdx.Value)] = strVal - - return nil -} - -type StringCircle struct { - objectImpl - Value []string -} - -func (o *StringCircle) TypeName() string { - return "string-circle" -} - -func (o *StringCircle) IndexGet(index objects.Object) (objects.Object, error) { - intIdx, ok := index.(*objects.Int) - if !ok { - return nil, objects.ErrInvalidIndexType - } - - r := int(intIdx.Value) % len(o.Value) - if r < 0 { - r = len(o.Value) + r - } - - return &objects.String{Value: o.Value[r]}, nil -} - -func (o *StringCircle) IndexSet(index, value objects.Object) error { - intIdx, ok := index.(*objects.Int) - if !ok { - return objects.ErrInvalidIndexType - } - - r := int(intIdx.Value) % len(o.Value) - if r < 0 { - r = len(o.Value) + r - } - - strVal, ok := objects.ToString(value) - if !ok { - return objects.ErrInvalidIndexValueType - } - - o.Value[r] = strVal - - return nil -} - -type StringArray struct { - Value []string -} - -func (o *StringArray) String() string { - return strings.Join(o.Value, ", ") -} - -func (o *StringArray) BinaryOp(op token.Token, rhs objects.Object) (objects.Object, error) { - if rhs, ok := rhs.(*StringArray); ok { - switch op { - case token.Add: - if len(rhs.Value) == 0 { - return o, nil - } - return &StringArray{Value: append(o.Value, rhs.Value...)}, nil - } - } - - return nil, objects.ErrInvalidOperator -} - -func (o *StringArray) IsFalsy() bool { - return len(o.Value) == 0 -} - -func (o *StringArray) Equals(x objects.Object) bool { - if x, ok := x.(*StringArray); ok { - if len(o.Value) != len(x.Value) { - return false - } - - for i, v := range o.Value { - if v != x.Value[i] { - return false - } - } - - return true - } - - return false -} - -func (o *StringArray) Copy() objects.Object { - return &StringArray{ - Value: append([]string{}, o.Value...), - } -} - -func (o *StringArray) TypeName() string { - return "string-array" -} - -func (o *StringArray) IndexGet(index objects.Object) (objects.Object, error) { - intIdx, ok := index.(*objects.Int) - if ok { - if intIdx.Value >= 0 && intIdx.Value < int64(len(o.Value)) { - return &objects.String{Value: o.Value[intIdx.Value]}, nil - } - - return nil, objects.ErrIndexOutOfBounds - } - - strIdx, ok := index.(*objects.String) - if ok { - for vidx, str := range o.Value { - if strIdx.Value == str { - return &objects.Int{Value: int64(vidx)}, nil - } - } - - return objects.UndefinedValue, nil - } - - return nil, objects.ErrInvalidIndexType -} - -func (o *StringArray) IndexSet(index, value objects.Object) error { - strVal, ok := objects.ToString(value) - if !ok { - return objects.ErrInvalidIndexValueType - } - - intIdx, ok := index.(*objects.Int) - if ok { - if intIdx.Value >= 0 && intIdx.Value < int64(len(o.Value)) { - o.Value[intIdx.Value] = strVal - return nil - } - - return objects.ErrIndexOutOfBounds - } - - return objects.ErrInvalidIndexType -} - -func (o *StringArray) Call(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 1 { - return nil, objects.ErrWrongNumArguments - } - - s1, ok := objects.ToString(args[0]) - if !ok { - return nil, objects.ErrInvalidArgumentType{ - Name: "first", - Expected: "string(compatible)", - Found: args[0].TypeName(), - } - } - - for i, v := range o.Value { - if v == s1 { - return &objects.Int{Value: int64(i)}, nil - } - } - - return objects.UndefinedValue, nil -} - -func TestIndexable(t *testing.T) { - dict := func() *StringDict { return &StringDict{Value: map[string]string{"a": "foo", "b": "bar"}} } - expect(t, `out = dict["a"]`, Opts().Symbol("dict", dict()).Skip2ndPass(), "foo") - expect(t, `out = dict["B"]`, Opts().Symbol("dict", dict()).Skip2ndPass(), "bar") - expect(t, `out = dict["x"]`, Opts().Symbol("dict", dict()).Skip2ndPass(), objects.UndefinedValue) - expectError(t, `dict[0]`, Opts().Symbol("dict", dict()).Skip2ndPass(), "invalid index type") - - strCir := func() *StringCircle { return &StringCircle{Value: []string{"one", "two", "three"}} } - expect(t, `out = cir[0]`, Opts().Symbol("cir", strCir()).Skip2ndPass(), "one") - expect(t, `out = cir[1]`, Opts().Symbol("cir", strCir()).Skip2ndPass(), "two") - expect(t, `out = cir[-1]`, Opts().Symbol("cir", strCir()).Skip2ndPass(), "three") - expect(t, `out = cir[-2]`, Opts().Symbol("cir", strCir()).Skip2ndPass(), "two") - expect(t, `out = cir[3]`, Opts().Symbol("cir", strCir()).Skip2ndPass(), "one") - expectError(t, `cir["a"]`, Opts().Symbol("cir", strCir()).Skip2ndPass(), "invalid index type") - - strArr := func() *StringArray { return &StringArray{Value: []string{"one", "two", "three"}} } - expect(t, `out = arr["one"]`, Opts().Symbol("arr", strArr()).Skip2ndPass(), 0) - expect(t, `out = arr["three"]`, Opts().Symbol("arr", strArr()).Skip2ndPass(), 2) - expect(t, `out = arr["four"]`, Opts().Symbol("arr", strArr()).Skip2ndPass(), objects.UndefinedValue) - expect(t, `out = arr[0]`, Opts().Symbol("arr", strArr()).Skip2ndPass(), "one") - expect(t, `out = arr[1]`, Opts().Symbol("arr", strArr()).Skip2ndPass(), "two") - expectError(t, `arr[-1]`, Opts().Symbol("arr", strArr()).Skip2ndPass(), "index out of bounds") -} - -func TestIndexAssignable(t *testing.T) { - dict := func() *StringDict { return &StringDict{Value: map[string]string{"a": "foo", "b": "bar"}} } - expect(t, `dict["a"] = "1984"; out = dict["a"]`, Opts().Symbol("dict", dict()).Skip2ndPass(), "1984") - expect(t, `dict["c"] = "1984"; out = dict["c"]`, Opts().Symbol("dict", dict()).Skip2ndPass(), "1984") - expect(t, `dict["c"] = 1984; out = dict["C"]`, Opts().Symbol("dict", dict()).Skip2ndPass(), "1984") - expectError(t, `dict[0] = "1984"`, Opts().Symbol("dict", dict()).Skip2ndPass(), "invalid index type") - - strCir := func() *StringCircle { return &StringCircle{Value: []string{"one", "two", "three"}} } - expect(t, `cir[0] = "ONE"; out = cir[0]`, Opts().Symbol("cir", strCir()).Skip2ndPass(), "ONE") - expect(t, `cir[1] = "TWO"; out = cir[1]`, Opts().Symbol("cir", strCir()).Skip2ndPass(), "TWO") - expect(t, `cir[-1] = "THREE"; out = cir[2]`, Opts().Symbol("cir", strCir()).Skip2ndPass(), "THREE") - expect(t, `cir[0] = "ONE"; out = cir[3]`, Opts().Symbol("cir", strCir()).Skip2ndPass(), "ONE") - expectError(t, `cir["a"] = "ONE"`, Opts().Symbol("cir", strCir()).Skip2ndPass(), "invalid index type") - - strArr := func() *StringArray { return &StringArray{Value: []string{"one", "two", "three"}} } - expect(t, `arr[0] = "ONE"; out = arr[0]`, Opts().Symbol("arr", strArr()).Skip2ndPass(), "ONE") - expect(t, `arr[1] = "TWO"; out = arr[1]`, Opts().Symbol("arr", strArr()).Skip2ndPass(), "TWO") - expectError(t, `arr["one"] = "ONE"`, Opts().Symbol("arr", strArr()).Skip2ndPass(), "invalid index type") -} diff --git a/runtime/vm_integer_test.go b/runtime/vm_integer_test.go deleted file mode 100644 index 4b1d78d..0000000 --- a/runtime/vm_integer_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package runtime_test - -import ( - "testing" -) - -func TestInteger(t *testing.T) { - expect(t, `out = 5`, nil, 5) - expect(t, `out = 10`, nil, 10) - expect(t, `out = -5`, nil, -5) - expect(t, `out = -10`, nil, -10) - expect(t, `out = 5 + 5 + 5 + 5 - 10`, nil, 10) - expect(t, `out = 2 * 2 * 2 * 2 * 2`, nil, 32) - expect(t, `out = -50 + 100 + -50`, nil, 0) - expect(t, `out = 5 * 2 + 10`, nil, 20) - expect(t, `out = 5 + 2 * 10`, nil, 25) - expect(t, `out = 20 + 2 * -10`, nil, 0) - expect(t, `out = 50 / 2 * 2 + 10`, nil, 60) - expect(t, `out = 2 * (5 + 10)`, nil, 30) - expect(t, `out = 3 * 3 * 3 + 10`, nil, 37) - expect(t, `out = 3 * (3 * 3) + 10`, nil, 37) - expect(t, `out = (5 + 10 * 2 + 15 /3) * 2 + -10`, nil, 50) - expect(t, `out = 5 % 3`, nil, 2) - expect(t, `out = 5 % 3 + 4`, nil, 6) - expect(t, `out = +5`, nil, 5) - expect(t, `out = +5 + -5`, nil, 0) - - expect(t, `out = 9 + '0'`, nil, '9') - expect(t, `out = '9' - 5`, nil, '4') -} diff --git a/runtime/vm_iterable_test.go b/runtime/vm_iterable_test.go deleted file mode 100644 index bddbf2c..0000000 --- a/runtime/vm_iterable_test.go +++ /dev/null @@ -1,44 +0,0 @@ -package runtime_test - -import ( - "testing" - - "github.com/d5/tengo/objects" -) - -type StringArrayIterator struct { - objectImpl - strArr *StringArray - idx int -} - -func (i *StringArrayIterator) TypeName() string { - return "string-array-iterator" -} - -func (i *StringArrayIterator) Next() bool { - i.idx++ - return i.idx <= len(i.strArr.Value) -} - -func (i *StringArrayIterator) Key() objects.Object { - return &objects.Int{Value: int64(i.idx - 1)} -} - -func (i *StringArrayIterator) Value() objects.Object { - return &objects.String{Value: i.strArr.Value[i.idx-1]} -} - -func (o *StringArray) Iterate() objects.Iterator { - return &StringArrayIterator{ - strArr: o, - } -} - -func TestIterable(t *testing.T) { - strArr := func() *StringArray { return &StringArray{Value: []string{"one", "two", "three"}} } - - expect(t, `for i, s in arr { out += i }`, Opts().Symbol("arr", strArr()).Skip2ndPass(), 3) - expect(t, `for i, s in arr { out += s }`, Opts().Symbol("arr", strArr()).Skip2ndPass(), "onetwothree") - expect(t, `for i, s in arr { out += s + i }`, Opts().Symbol("arr", strArr()).Skip2ndPass(), "one0two1three2") -} diff --git a/runtime/vm_logic_test.go b/runtime/vm_logic_test.go deleted file mode 100644 index 24f98b0..0000000 --- a/runtime/vm_logic_test.go +++ /dev/null @@ -1,41 +0,0 @@ -package runtime_test - -import "testing" - -func TestLogical(t *testing.T) { - expect(t, `out = true && true`, nil, true) - expect(t, `out = true && false`, nil, false) - expect(t, `out = false && true`, nil, false) - expect(t, `out = false && false`, nil, false) - expect(t, `out = !true && true`, nil, false) - expect(t, `out = !true && false`, nil, false) - expect(t, `out = !false && true`, nil, true) - expect(t, `out = !false && false`, nil, false) - - expect(t, `out = true || true`, nil, true) - expect(t, `out = true || false`, nil, true) - expect(t, `out = false || true`, nil, true) - expect(t, `out = false || false`, nil, false) - expect(t, `out = !true || true`, nil, true) - expect(t, `out = !true || false`, nil, false) - expect(t, `out = !false || true`, nil, true) - expect(t, `out = !false || false`, nil, true) - - expect(t, `out = 1 && 2`, nil, 2) - expect(t, `out = 1 || 2`, nil, 1) - expect(t, `out = 1 && 0`, nil, 0) - expect(t, `out = 1 || 0`, nil, 1) - expect(t, `out = 1 && (0 || 2)`, nil, 2) - expect(t, `out = 0 || (0 || 2)`, nil, 2) - expect(t, `out = 0 || (0 && 2)`, nil, 0) - expect(t, `out = 0 || (2 && 0)`, nil, 0) - - expect(t, `t:=func() {out = 3; return true}; f:=func() {out = 7; return false}; t() && f()`, nil, 7) - expect(t, `t:=func() {out = 3; return true}; f:=func() {out = 7; return false}; f() && t()`, nil, 7) - expect(t, `t:=func() {out = 3; return true}; f:=func() {out = 7; return false}; f() || t()`, nil, 3) - expect(t, `t:=func() {out = 3; return true}; f:=func() {out = 7; return false}; t() || f()`, nil, 3) - expect(t, `t:=func() {out = 3; return true}; f:=func() {out = 7; return false}; !t() && f()`, nil, 3) - expect(t, `t:=func() {out = 3; return true}; f:=func() {out = 7; return false}; !f() && t()`, nil, 3) - expect(t, `t:=func() {out = 3; return true}; f:=func() {out = 7; return false}; !f() || t()`, nil, 7) - expect(t, `t:=func() {out = 3; return true}; f:=func() {out = 7; return false}; !t() || f()`, nil, 7) -} diff --git a/runtime/vm_map_test.go b/runtime/vm_map_test.go deleted file mode 100644 index 4ade676..0000000 --- a/runtime/vm_map_test.go +++ /dev/null @@ -1,51 +0,0 @@ -package runtime_test - -import ( - "testing" - - "github.com/d5/tengo/objects" -) - -func TestMap(t *testing.T) { - expect(t, ` -out = { - one: 10 - 9, - two: 1 + 1, - three: 6 / 2 -}`, nil, MAP{ - "one": 1, - "two": 2, - "three": 3, - }) - - expect(t, ` -out = { - "one": 10 - 9, - "two": 1 + 1, - "three": 6 / 2 -}`, nil, MAP{ - "one": 1, - "two": 2, - "three": 3, - }) - - expect(t, `out = {foo: 5}["foo"]`, nil, 5) - expect(t, `out = {foo: 5}["bar"]`, nil, objects.UndefinedValue) - expect(t, `key := "foo"; out = {foo: 5}[key]`, nil, 5) - expect(t, `out = {}["foo"]`, nil, objects.UndefinedValue) - - expect(t, ` -m := { - foo: func(x) { - return x * 2 - } -} -out = m["foo"](2) + m["foo"](3) -`, nil, 10) - - // map assignment is copy-by-reference - expect(t, `m1 := {k1: 1, k2: "foo"}; m2 := m1; m1.k1 = 5; out = m2.k1`, nil, 5) - expect(t, `m1 := {k1: 1, k2: "foo"}; m2 := m1; m2.k1 = 3; out = m1.k1`, nil, 3) - expect(t, `func() { m1 := {k1: 1, k2: "foo"}; m2 := m1; m1.k1 = 5; out = m2.k1 }()`, nil, 5) - expect(t, `func() { m1 := {k1: 1, k2: "foo"}; m2 := m1; m2.k1 = 3; out = m1.k1 }()`, nil, 3) -} diff --git a/runtime/vm_module_test.go b/runtime/vm_module_test.go deleted file mode 100644 index 97a355d..0000000 --- a/runtime/vm_module_test.go +++ /dev/null @@ -1,214 +0,0 @@ -package runtime_test - -import ( - "math" - "math/rand" - "testing" - - "github.com/d5/tengo/objects" -) - -func TestBuiltin(t *testing.T) { - m := Opts().Module("math", - &objects.BuiltinModule{ - Attrs: map[string]objects.Object{ - "abs": &objects.UserFunction{ - Name: "abs", - Value: func(args ...objects.Object) (ret objects.Object, err error) { - v, _ := objects.ToFloat64(args[0]) - return &objects.Float{Value: math.Abs(v)}, nil - }, - }, - }, - }) - - // builtin - expect(t, `math := import("math"); out = math.abs(1)`, m, 1.0) - expect(t, `math := import("math"); out = math.abs(-1)`, m, 1.0) - expect(t, `math := import("math"); out = math.abs(1.0)`, m, 1.0) - expect(t, `math := import("math"); out = math.abs(-1.0)`, m, 1.0) -} - -func TestUserModules(t *testing.T) { - // export none - expect(t, `out = import("mod1")`, Opts().Module("mod1", `fn := func() { return 5.0 }; a := 2`), objects.UndefinedValue) - - // export values - expect(t, `out = import("mod1")`, Opts().Module("mod1", `export 5`), 5) - expect(t, `out = import("mod1")`, Opts().Module("mod1", `export "foo"`), "foo") - - // export compound types - expect(t, `out = import("mod1")`, Opts().Module("mod1", `export [1, 2, 3]`), IARR{1, 2, 3}) - expect(t, `out = import("mod1")`, Opts().Module("mod1", `export {a: 1, b: 2}`), IMAP{"a": 1, "b": 2}) - - // export value is immutable - expectError(t, `m1 := import("mod1"); m1.a = 5`, Opts().Module("mod1", `export {a: 1, b: 2}`), "not index-assignable") - expectError(t, `m1 := import("mod1"); m1[1] = 5`, Opts().Module("mod1", `export [1, 2, 3]`), "not index-assignable") - - // code after export statement will not be executed - expect(t, `out = import("mod1")`, Opts().Module("mod1", `a := 10; export a; a = 20`), 10) - expect(t, `out = import("mod1")`, Opts().Module("mod1", `a := 10; export a; a = 20; export a`), 10) - - // export function - expect(t, `out = import("mod1")()`, Opts().Module("mod1", `export func() { return 5.0 }`), 5.0) - // export function that reads module-global variable - expect(t, `out = import("mod1")()`, Opts().Module("mod1", `a := 1.5; export func() { return a + 5.0 }`), 6.5) - // export function that read local variable - expect(t, `out = import("mod1")()`, Opts().Module("mod1", `export func() { a := 1.5; return a + 5.0 }`), 6.5) - // export function that read free variables - expect(t, `out = import("mod1")()`, Opts().Module("mod1", `export func() { a := 1.5; return func() { return a + 5.0 }() }`), 6.5) - - // recursive function in module - expect(t, `out = import("mod1")`, Opts().Module( - "mod1", ` -a := func(x) { - return x == 0 ? 0 : x + a(x-1) -} - -export a(5) -`), 15) - expect(t, `out = import("mod1")`, Opts().Module( - "mod1", ` -export func() { - a := func(x) { - return x == 0 ? 0 : x + a(x-1) - } - - return a(5) -}() -`), 15) - - // (main) -> mod1 -> mod2 - expect(t, `out = import("mod1")()`, - Opts().Module("mod1", `export import("mod2")`). - Module("mod2", `export func() { return 5.0 }`), - 5.0) - // (main) -> mod1 -> mod2 - // -> mod2 - expect(t, `import("mod1"); out = import("mod2")()`, - Opts().Module("mod1", `export import("mod2")`). - Module("mod2", `export func() { return 5.0 }`), - 5.0) - // (main) -> mod1 -> mod2 -> mod3 - // -> mod2 -> mod3 - expect(t, `import("mod1"); out = import("mod2")()`, - Opts().Module("mod1", `export import("mod2")`). - Module("mod2", `export import("mod3")`). - Module("mod3", `export func() { return 5.0 }`), - 5.0) - - // cyclic imports - // (main) -> mod1 -> mod2 -> mod1 - expectError(t, `import("mod1")`, - Opts().Module("mod1", `import("mod2")`). - Module("mod2", `import("mod1")`), - "Compile Error: cyclic module import: mod1\n\tat mod2:1:1") - // (main) -> mod1 -> mod2 -> mod3 -> mod1 - expectError(t, `import("mod1")`, - Opts().Module("mod1", `import("mod2")`). - Module("mod2", `import("mod3")`). - Module("mod3", `import("mod1")`), - "Compile Error: cyclic module import: mod1\n\tat mod3:1:1") - // (main) -> mod1 -> mod2 -> mod3 -> mod2 - expectError(t, `import("mod1")`, - Opts().Module("mod1", `import("mod2")`). - Module("mod2", `import("mod3")`). - Module("mod3", `import("mod2")`), - "Compile Error: cyclic module import: mod2\n\tat mod3:1:1") - - // unknown modules - expectError(t, `import("mod0")`, Opts().Module("mod1", `a := 5`), "module 'mod0' not found") - expectError(t, `import("mod1")`, Opts().Module("mod1", `import("mod2")`), "module 'mod2' not found") - - // module is immutable but its variables is not necessarily immutable. - expect(t, `m1 := import("mod1"); m1.a.b = 5; out = m1.a.b`, - Opts().Module("mod1", `export {a: {b: 3}}`), - 5) - - // make sure module has same builtin functions - expect(t, `out = import("mod1")`, - Opts().Module("mod1", `export func() { return type_name(0) }()`), - "int") - - // 'export' statement is ignored outside module - expect(t, `a := 5; export func() { a = 10 }(); out = a`, Opts().Skip2ndPass(), 5) - - // 'export' must be in the top-level - expectError(t, `import("mod1")`, - Opts().Module("mod1", `func() { export 5 }()`), - "Compile Error: export not allowed inside function\n\tat mod1:1:10") - expectError(t, `import("mod1")`, - Opts().Module("mod1", `func() { func() { export 5 }() }()`), - "Compile Error: export not allowed inside function\n\tat mod1:1:19") - - // module cannot access outer scope - expectError(t, `a := 5; import("mod1")`, - Opts().Module("mod1", `export a`), - "Compile Error: unresolved reference 'a'\n\tat mod1:1:8") - - // runtime error within modules - expectError(t, ` -a := 1; -b := import("mod1"); -b(a)`, - Opts().Module("mod1", ` -export func(a) { - a() -} -`), "Runtime Error: not callable: int\n\tat mod1:3:4\n\tat test:4:1") - - // module skipping export - expect(t, `out = import("mod0")`, Opts().Module("mod0", ``), objects.UndefinedValue) - expect(t, `out = import("mod0")`, Opts().Module("mod0", `if 1 { export true }`), true) - expect(t, `out = import("mod0")`, Opts().Module("mod0", `if 0 { export true }`), objects.UndefinedValue) - expect(t, `out = import("mod0")`, Opts().Module("mod0", `if 1 { } else { export true }`), objects.UndefinedValue) - expect(t, `out = import("mod0")`, Opts().Module("mod0", `for v:=0;;v++ { if v == 3 { export true } } }`), true) - expect(t, `out = import("mod0")`, Opts().Module("mod0", `for v:=0;;v++ { if v == 3 { break } } }`), objects.UndefinedValue) -} - -func TestModuleBlockScopes(t *testing.T) { - m := Opts().Module("rand", - &objects.BuiltinModule{ - Attrs: map[string]objects.Object{ - "intn": &objects.UserFunction{ - Name: "abs", - Value: func(args ...objects.Object) (ret objects.Object, err error) { - v, _ := objects.ToInt64(args[0]) - return &objects.Int{Value: rand.Int63n(v)}, nil - }, - }, - }, - }) - - // block scopes in module - expect(t, `out = import("mod1")()`, m.Module( - "mod1", ` - rand := import("rand") - foo := func() { return 1 } - export func() { - rand.intn(3) - return foo() - }`), 1) - - expect(t, `out = import("mod1")()`, m.Module( - "mod1", ` -rand := import("rand") -foo := func() { return 1 } -export func() { - rand.intn(3) - if foo() {} - return 10 -} -`), 10) - - expect(t, `out = import("mod1")()`, m.Module( - "mod1", ` - rand := import("rand") - foo := func() { return 1 } - export func() { - rand.intn(3) - if true { foo() } - return 10 - } - `), 10) -} diff --git a/runtime/vm_not_operator_test.go b/runtime/vm_not_operator_test.go deleted file mode 100644 index 9b6123b..0000000 --- a/runtime/vm_not_operator_test.go +++ /dev/null @@ -1,15 +0,0 @@ -package runtime_test - -import ( - "testing" -) - -func TestBangOperator(t *testing.T) { - expect(t, `out = !true`, nil, false) - expect(t, `out = !false`, nil, true) - expect(t, `out = !0`, nil, true) - expect(t, `out = !5`, nil, false) - expect(t, `out = !!true`, nil, true) - expect(t, `out = !!false`, nil, false) - expect(t, `out = !!5`, nil, true) -} diff --git a/runtime/vm_objects_limit_test.go b/runtime/vm_objects_limit_test.go deleted file mode 100644 index 877c941..0000000 --- a/runtime/vm_objects_limit_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package runtime_test - -import ( - "testing" - - "github.com/d5/tengo/objects" -) - -func TestObjectsLimit(t *testing.T) { - testAllocsLimit(t, `5`, 0) - testAllocsLimit(t, `5 + 5`, 1) - testAllocsLimit(t, `a := [1, 2, 3]`, 1) - testAllocsLimit(t, `a := 1; b := 2; c := 3; d := [a, b, c]`, 1) - testAllocsLimit(t, `a := {foo: 1, bar: 2}`, 1) - testAllocsLimit(t, `a := 1; b := 2; c := {foo: a, bar: b}`, 1) - testAllocsLimit(t, ` -f := func() { - return 5 + 5 -} -a := f() + 5 -`, 2) - testAllocsLimit(t, ` -f := func() { - return 5 + 5 -} -a := f() -`, 1) - testAllocsLimit(t, ` -a := [] -f := func() { - a = append(a, 5) -} -f() -f() -f() -`, 4) -} - -func testAllocsLimit(t *testing.T, src string, limit int64) { - expect(t, src, Opts().Skip2ndPass(), objects.UndefinedValue) // no limit - expect(t, src, Opts().MaxAllocs(limit).Skip2ndPass(), objects.UndefinedValue) - expect(t, src, Opts().MaxAllocs(limit+1).Skip2ndPass(), objects.UndefinedValue) - if limit > 1 { - expectError(t, src, Opts().MaxAllocs(limit-1).Skip2ndPass(), "allocation limit exceeded") - } - if limit > 2 { - expectError(t, src, Opts().MaxAllocs(limit-2).Skip2ndPass(), "allocation limit exceeded") - } -} diff --git a/runtime/vm_return_test.go b/runtime/vm_return_test.go deleted file mode 100644 index 5251429..0000000 --- a/runtime/vm_return_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package runtime_test - -import ( - "testing" -) - -func TestReturn(t *testing.T) { - expect(t, `out = func() { return 10; }()`, nil, 10) - expect(t, `out = func() { return 10; return 9; }()`, nil, 10) - expect(t, `out = func() { return 2 * 5; return 9 }()`, nil, 10) - expect(t, `out = func() { 9; return 2 * 5; return 9 }()`, nil, 10) - expect(t, ` - out = func() { - if (10 > 1) { - if (10 > 1) { - return 10; - } - - return 1; - } - }()`, nil, 10) - - expect(t, `f1 := func() { return 2 * 5; }; out = f1()`, nil, 10) -} diff --git a/runtime/vm_scopes_test.go b/runtime/vm_scopes_test.go deleted file mode 100644 index 43e8613..0000000 --- a/runtime/vm_scopes_test.go +++ /dev/null @@ -1,103 +0,0 @@ -package runtime_test - -import "testing" - -func TestVMScopes(t *testing.T) { - // shadowed global variable - expect(t, ` -c := 5 -if a := 3; a { - c := 6 -} else { - c := 7 -} -out = c -`, nil, 5) - - // shadowed local variable - expect(t, ` -func() { - c := 5 - if a := 3; a { - c := 6 - } else { - c := 7 - } - out = c -}() -`, nil, 5) - - // 'b' is declared in 2 separate blocks - expect(t, ` -c := 5 -if a := 3; a { - b := 8 - c = b -} else { - b := 9 - c = b -} -out = c -`, nil, 8) - - // shadowing inside for statement - expect(t, ` -a := 4 -b := 5 -for i:=0;i<3;i++ { - b := 6 - for j:=0;j<2;j++ { - b := 7 - a = i*j - } -} -out = a`, nil, 2) - - // shadowing variable declared in init statement - expect(t, ` -if a := 5; a { - a := 6 - out = a -}`, nil, 6) - expect(t, ` -a := 4 -if a := 5; a { - a := 6 - out = a -}`, nil, 6) - expect(t, ` -a := 4 -if a := 0; a { - a := 6 - out = a -} else { - a := 7 - out = a -}`, nil, 7) - expect(t, ` -a := 4 -if a := 0; a { - out = a -} else { - out = a -}`, nil, 0) - - // shadowing function level - expect(t, ` -a := 5 -func() { - a := 6 - a = 7 -}() -out = a -`, nil, 5) - expect(t, ` -a := 5 -func() { - if a := 7; true { - a = 8 - } -}() -out = a -`, nil, 5) -} diff --git a/runtime/vm_selector_test.go b/runtime/vm_selector_test.go deleted file mode 100644 index 9461cd9..0000000 --- a/runtime/vm_selector_test.go +++ /dev/null @@ -1,90 +0,0 @@ -package runtime_test - -import ( - "testing" - - "github.com/d5/tengo/objects" -) - -func TestSelector(t *testing.T) { - expect(t, `a := {k1: 5, k2: "foo"}; out = a.k1`, nil, 5) - expect(t, `a := {k1: 5, k2: "foo"}; out = a.k2`, nil, "foo") - expect(t, `a := {k1: 5, k2: "foo"}; out = a.k3`, nil, objects.UndefinedValue) - - expect(t, ` -a := { - b: { - c: 4, - a: false - }, - c: "foo bar" -} -out = a.b.c`, nil, 4) - - expect(t, ` -a := { - b: { - c: 4, - a: false - }, - c: "foo bar" -} -b := a.x.c`, nil, objects.UndefinedValue) - - expect(t, ` -a := { - b: { - c: 4, - a: false - }, - c: "foo bar" -} -b := a.x.y`, nil, objects.UndefinedValue) - - expect(t, `a := {b: 1, c: "foo"}; a.b = 2; out = a.b`, nil, 2) - expect(t, `a := {b: 1, c: "foo"}; a.c = 2; out = a.c`, nil, 2) // type not checked on sub-field - expect(t, `a := {b: {c: 1}}; a.b.c = 2; out = a.b.c`, nil, 2) - expect(t, `a := {b: 1}; a.c = 2; out = a`, nil, MAP{"b": 1, "c": 2}) - expect(t, `a := {b: {c: 1}}; a.b.d = 2; out = a`, nil, MAP{"b": MAP{"c": 1, "d": 2}}) - - expect(t, `func() { a := {b: 1, c: "foo"}; a.b = 2; out = a.b }()`, nil, 2) - expect(t, `func() { a := {b: 1, c: "foo"}; a.c = 2; out = a.c }()`, nil, 2) // type not checked on sub-field - expect(t, `func() { a := {b: {c: 1}}; a.b.c = 2; out = a.b.c }()`, nil, 2) - expect(t, `func() { a := {b: 1}; a.c = 2; out = a }()`, nil, MAP{"b": 1, "c": 2}) - expect(t, `func() { a := {b: {c: 1}}; a.b.d = 2; out = a }()`, nil, MAP{"b": MAP{"c": 1, "d": 2}}) - - expect(t, `func() { a := {b: 1, c: "foo"}; func() { a.b = 2 }(); out = a.b }()`, nil, 2) - expect(t, `func() { a := {b: 1, c: "foo"}; func() { a.c = 2 }(); out = a.c }()`, nil, 2) // type not checked on sub-field - expect(t, `func() { a := {b: {c: 1}}; func() { a.b.c = 2 }(); out = a.b.c }()`, nil, 2) - expect(t, `func() { a := {b: 1}; func() { a.c = 2 }(); out = a }()`, nil, MAP{"b": 1, "c": 2}) - expect(t, `func() { a := {b: {c: 1}}; func() { a.b.d = 2 }(); out = a }()`, nil, MAP{"b": MAP{"c": 1, "d": 2}}) - - expect(t, ` -a := { - b: [1, 2, 3], - c: { - d: 8, - e: "foo", - f: [9, 8] - } -} -out = [a.b[2], a.c.d, a.c.e, a.c.f[1]] -`, nil, ARR{3, 8, "foo", 8}) - - expect(t, ` -func() { - a := [1, 2, 3] - b := 9 - a[1] = b - b = 7 // make sure a[1] has a COPY of value of 'b' - out = a[1] -}() -`, nil, 9) - - expectError(t, `a := {b: {c: 1}}; a.d.c = 2`, nil, "not index-assignable") - expectError(t, `a := [1, 2, 3]; a.b = 2`, nil, "invalid index type") - expectError(t, `a := "foo"; a.b = 2`, nil, "not index-assignable") - expectError(t, `func() { a := {b: {c: 1}}; a.d.c = 2 }()`, nil, "not index-assignable") - expectError(t, `func() { a := [1, 2, 3]; a.b = 2 }()`, nil, "invalid index type") - expectError(t, `func() { a := "foo"; a.b = 2 }()`, nil, "not index-assignable") -} diff --git a/runtime/vm_source_modules_test.go b/runtime/vm_source_modules_test.go deleted file mode 100644 index 660170c..0000000 --- a/runtime/vm_source_modules_test.go +++ /dev/null @@ -1,142 +0,0 @@ -package runtime_test - -import ( - "testing" - - "github.com/d5/tengo/objects" - "github.com/d5/tengo/stdlib" -) - -func TestSourceModules(t *testing.T) { - testEnumModule(t, `out = enum.key(0, 20)`, 0) - testEnumModule(t, `out = enum.key(10, 20)`, 10) - testEnumModule(t, `out = enum.value(0, 0)`, 0) - testEnumModule(t, `out = enum.value(10, 20)`, 20) - - testEnumModule(t, `out = enum.all([], enum.value)`, true) - testEnumModule(t, `out = enum.all([1], enum.value)`, true) - testEnumModule(t, `out = enum.all([true, 1], enum.value)`, true) - testEnumModule(t, `out = enum.all([true, 0], enum.value)`, false) - testEnumModule(t, `out = enum.all([true, 0, 1], enum.value)`, false) - testEnumModule(t, `out = enum.all(immutable([true, 0, 1]), enum.value)`, false) // immutable-array - testEnumModule(t, `out = enum.all({}, enum.value)`, true) - testEnumModule(t, `out = enum.all({a:1}, enum.value)`, true) - testEnumModule(t, `out = enum.all({a:true, b:1}, enum.value)`, true) - testEnumModule(t, `out = enum.all(immutable({a:true, b:1}), enum.value)`, true) // immutable-map - testEnumModule(t, `out = enum.all({a:true, b:0}, enum.value)`, false) - testEnumModule(t, `out = enum.all({a:true, b:0, c:1}, enum.value)`, false) - testEnumModule(t, `out = enum.all(0, enum.value)`, objects.UndefinedValue) // non-enumerable: undefined - testEnumModule(t, `out = enum.all("123", enum.value)`, objects.UndefinedValue) // non-enumerable: undefined - - testEnumModule(t, `out = enum.any([], enum.value)`, false) - testEnumModule(t, `out = enum.any([1], enum.value)`, true) - testEnumModule(t, `out = enum.any([true, 1], enum.value)`, true) - testEnumModule(t, `out = enum.any([true, 0], enum.value)`, true) - testEnumModule(t, `out = enum.any([true, 0, 1], enum.value)`, true) - testEnumModule(t, `out = enum.any(immutable([true, 0, 1]), enum.value)`, true) // immutable-array - testEnumModule(t, `out = enum.any([false], enum.value)`, false) - testEnumModule(t, `out = enum.any([false, 0], enum.value)`, false) - testEnumModule(t, `out = enum.any({}, enum.value)`, false) - testEnumModule(t, `out = enum.any({a:1}, enum.value)`, true) - testEnumModule(t, `out = enum.any({a:true, b:1}, enum.value)`, true) - testEnumModule(t, `out = enum.any({a:true, b:0}, enum.value)`, true) - testEnumModule(t, `out = enum.any({a:true, b:0, c:1}, enum.value)`, true) - testEnumModule(t, `out = enum.any(immutable({a:true, b:0, c:1}), enum.value)`, true) // immutable-map - testEnumModule(t, `out = enum.any({a:false}, enum.value)`, false) - testEnumModule(t, `out = enum.any({a:false, b:0}, enum.value)`, false) - testEnumModule(t, `out = enum.any(0, enum.value)`, objects.UndefinedValue) // non-enumerable: undefined - testEnumModule(t, `out = enum.any("123", enum.value)`, objects.UndefinedValue) // non-enumerable: undefined - - testEnumModule(t, `out = enum.chunk([], 1)`, ARR{}) - testEnumModule(t, `out = enum.chunk([1], 1)`, ARR{ARR{1}}) - testEnumModule(t, `out = enum.chunk([1,2,3], 1)`, ARR{ARR{1}, ARR{2}, ARR{3}}) - testEnumModule(t, `out = enum.chunk([1,2,3], 2)`, ARR{ARR{1, 2}, ARR{3}}) - testEnumModule(t, `out = enum.chunk([1,2,3], 3)`, ARR{ARR{1, 2, 3}}) - testEnumModule(t, `out = enum.chunk([1,2,3], 4)`, ARR{ARR{1, 2, 3}}) - testEnumModule(t, `out = enum.chunk([1,2,3,4], 3)`, ARR{ARR{1, 2, 3}, ARR{4}}) - testEnumModule(t, `out = enum.chunk([], 0)`, objects.UndefinedValue) // size=0: undefined - testEnumModule(t, `out = enum.chunk([1], 0)`, objects.UndefinedValue) // size=0: undefined - testEnumModule(t, `out = enum.chunk([1,2,3], 0)`, objects.UndefinedValue) // size=0: undefined - testEnumModule(t, `out = enum.chunk({a:1,b:2,c:3}, 1)`, objects.UndefinedValue) // map: undefined - testEnumModule(t, `out = enum.chunk(0, 1)`, objects.UndefinedValue) // non-enumerable: undefined - testEnumModule(t, `out = enum.chunk("123", 1)`, objects.UndefinedValue) // non-enumerable: undefined - - testEnumModule(t, `out = enum.at([], 0)`, objects.UndefinedValue) - testEnumModule(t, `out = enum.at([], 1)`, objects.UndefinedValue) - testEnumModule(t, `out = enum.at([], -1)`, objects.UndefinedValue) - testEnumModule(t, `out = enum.at(["one"], 0)`, "one") - testEnumModule(t, `out = enum.at(["one"], 1)`, objects.UndefinedValue) - testEnumModule(t, `out = enum.at(["one"], -1)`, objects.UndefinedValue) - testEnumModule(t, `out = enum.at(["one","two","three"], 0)`, "one") - testEnumModule(t, `out = enum.at(["one","two","three"], 1)`, "two") - testEnumModule(t, `out = enum.at(["one","two","three"], 2)`, "three") - testEnumModule(t, `out = enum.at(["one","two","three"], -1)`, objects.UndefinedValue) - testEnumModule(t, `out = enum.at(["one","two","three"], 3)`, objects.UndefinedValue) - testEnumModule(t, `out = enum.at(["one","two","three"], "1")`, objects.UndefinedValue) // non-int index: undefined - testEnumModule(t, `out = enum.at({}, "a")`, objects.UndefinedValue) - testEnumModule(t, `out = enum.at({a:"one"}, "a")`, "one") - testEnumModule(t, `out = enum.at({a:"one"}, "b")`, objects.UndefinedValue) - testEnumModule(t, `out = enum.at({a:"one",b:"two",c:"three"}, "a")`, "one") - testEnumModule(t, `out = enum.at({a:"one",b:"two",c:"three"}, "b")`, "two") - testEnumModule(t, `out = enum.at({a:"one",b:"two",c:"three"}, "c")`, "three") - testEnumModule(t, `out = enum.at({a:"one",b:"two",c:"three"}, "d")`, objects.UndefinedValue) - testEnumModule(t, `out = enum.at({a:"one",b:"two",c:"three"}, 'a')`, objects.UndefinedValue) // non-string index: undefined - testEnumModule(t, `out = enum.at(0, 1)`, objects.UndefinedValue) // non-enumerable: undefined - testEnumModule(t, `out = enum.at("abc", 1)`, objects.UndefinedValue) // non-enumerable: undefined - - testEnumModule(t, `out=0; enum.each([],func(k,v){out+=v})`, 0) - testEnumModule(t, `out=0; enum.each([1,2,3],func(k,v){out+=v})`, 6) - testEnumModule(t, `out=0; enum.each([1,2,3],func(k,v){out+=k})`, 3) - testEnumModule(t, `out=0; enum.each({a:1,b:2,c:3},func(k,v){out+=v})`, 6) - testEnumModule(t, `out=""; enum.each({a:1,b:2,c:3},func(k,v){out+=k}); out=len(out)`, 3) - testEnumModule(t, `out=0; enum.each(5,func(k,v){out+=v})`, 0) // non-enumerable: no iteration - testEnumModule(t, `out=0; enum.each("123",func(k,v){out+=v})`, 0) // non-enumerable: no iteration - - testEnumModule(t, `out = enum.filter([], enum.value)`, ARR{}) - testEnumModule(t, `out = enum.filter([false,1,2], enum.value)`, ARR{1, 2}) - testEnumModule(t, `out = enum.filter([false,1,0,2], enum.value)`, ARR{1, 2}) - testEnumModule(t, `out = enum.filter({}, enum.value)`, objects.UndefinedValue) // non-array: undefined - testEnumModule(t, `out = enum.filter(0, enum.value)`, objects.UndefinedValue) // non-array: undefined - testEnumModule(t, `out = enum.filter("123", enum.value)`, objects.UndefinedValue) // non-array: undefined - - testEnumModule(t, `out = enum.find([], enum.value)`, objects.UndefinedValue) - testEnumModule(t, `out = enum.find([0], enum.value)`, objects.UndefinedValue) - testEnumModule(t, `out = enum.find([1], enum.value)`, 1) - testEnumModule(t, `out = enum.find([false,0,undefined,1], enum.value)`, 1) - testEnumModule(t, `out = enum.find([1,2,3], enum.value)`, 1) - testEnumModule(t, `out = enum.find({}, enum.value)`, objects.UndefinedValue) - testEnumModule(t, `out = enum.find({a:0}, enum.value)`, objects.UndefinedValue) - testEnumModule(t, `out = enum.find({a:1}, enum.value)`, 1) - testEnumModule(t, `out = enum.find({a:false,b:0,c:undefined,d:1}, enum.value)`, 1) - //testEnumModule(t, `out = enum.find({a:1,b:2,c:3}, enum.value)`, 1) - testEnumModule(t, `out = enum.find(0, enum.value)`, objects.UndefinedValue) // non-enumerable: undefined - testEnumModule(t, `out = enum.find("123", enum.value)`, objects.UndefinedValue) // non-enumerable: undefined - - testEnumModule(t, `out = enum.find_key([], enum.value)`, objects.UndefinedValue) - testEnumModule(t, `out = enum.find_key([0], enum.value)`, objects.UndefinedValue) - testEnumModule(t, `out = enum.find_key([1], enum.value)`, 0) - testEnumModule(t, `out = enum.find_key([false,0,undefined,1], enum.value)`, 3) - testEnumModule(t, `out = enum.find_key([1,2,3], enum.value)`, 0) - testEnumModule(t, `out = enum.find_key({}, enum.value)`, objects.UndefinedValue) - testEnumModule(t, `out = enum.find_key({a:0}, enum.value)`, objects.UndefinedValue) - testEnumModule(t, `out = enum.find_key({a:1}, enum.value)`, "a") - testEnumModule(t, `out = enum.find_key({a:false,b:0,c:undefined,d:1}, enum.value)`, "d") - //testEnumModule(t, `out = enum.find_key({a:1,b:2,c:3}, enum.value)`, "a") - testEnumModule(t, `out = enum.find_key(0, enum.value)`, objects.UndefinedValue) // non-enumerable: undefined - testEnumModule(t, `out = enum.find_key("123", enum.value)`, objects.UndefinedValue) // non-enumerable: undefined - - testEnumModule(t, `out = enum.map([], enum.value)`, ARR{}) - testEnumModule(t, `out = enum.map([1,2,3], enum.value)`, ARR{1, 2, 3}) - testEnumModule(t, `out = enum.map([1,2,3], enum.key)`, ARR{0, 1, 2}) - testEnumModule(t, `out = enum.map([1,2,3], func(k,v) { return v*2 })`, ARR{2, 4, 6}) - testEnumModule(t, `out = enum.map({}, enum.value)`, ARR{}) - testEnumModule(t, `out = enum.map({a:1}, func(k,v) { return v*2 })`, ARR{2}) - testEnumModule(t, `out = enum.map(0, enum.value)`, objects.UndefinedValue) // non-enumerable: undefined - testEnumModule(t, `out = enum.map("123", enum.value)`, objects.UndefinedValue) // non-enumerable: undefined -} - -func testEnumModule(t *testing.T, input string, expected interface{}) { - expect(t, `enum := import("enum"); `+input, - Opts().Module("enum", stdlib.SourceModules["enum"]), - expected) -} diff --git a/runtime/vm_srcmod_test.go b/runtime/vm_srcmod_test.go deleted file mode 100644 index e7a0f6f..0000000 --- a/runtime/vm_srcmod_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package runtime_test - -import "testing" - -func TestSrcModEnum(t *testing.T) { - expect(t, ` -x := import("enum") -out = x.all([1, 2, 3], func(_, v) { return v >= 1 }) -`, Opts().Stdlib(), true) - expect(t, ` -x := import("enum") -out = x.all([1, 2, 3], func(_, v) { return v >= 2 }) -`, Opts().Stdlib(), false) - - expect(t, ` -x := import("enum") -out = x.any([1, 2, 3], func(_, v) { return v >= 1 }) -`, Opts().Stdlib(), true) - expect(t, ` -x := import("enum") -out = x.any([1, 2, 3], func(_, v) { return v >= 2 }) -`, Opts().Stdlib(), true) - - expect(t, ` -x := import("enum") -out = x.chunk([1, 2, 3], 1) -`, Opts().Stdlib(), ARR{ARR{1}, ARR{2}, ARR{3}}) - expect(t, ` -x := import("enum") -out = x.chunk([1, 2, 3], 2) -`, Opts().Stdlib(), ARR{ARR{1, 2}, ARR{3}}) - expect(t, ` -x := import("enum") -out = x.chunk([1, 2, 3], 3) -`, Opts().Stdlib(), ARR{ARR{1, 2, 3}}) - expect(t, ` -x := import("enum") -out = x.chunk([1, 2, 3], 4) -`, Opts().Stdlib(), ARR{ARR{1, 2, 3}}) - expect(t, ` -x := import("enum") -out = x.chunk([1, 2, 3, 4, 5, 6], 2) -`, Opts().Stdlib(), ARR{ARR{1, 2}, ARR{3, 4}, ARR{5, 6}}) - - expect(t, ` -x := import("enum") -out = x.at([1, 2, 3], 0) -`, Opts().Stdlib(), 1) -} diff --git a/runtime/vm_stack_overflow_test.go b/runtime/vm_stack_overflow_test.go deleted file mode 100644 index edc92d1..0000000 --- a/runtime/vm_stack_overflow_test.go +++ /dev/null @@ -1,7 +0,0 @@ -package runtime_test - -import "testing" - -func TestVMStackOverflow(t *testing.T) { - expectError(t, `f := func() { return f() + 1 }; f()`, nil, "stack overflow") -} diff --git a/runtime/vm_string_test.go b/runtime/vm_string_test.go deleted file mode 100644 index f1b9c46..0000000 --- a/runtime/vm_string_test.go +++ /dev/null @@ -1,73 +0,0 @@ -package runtime_test - -import ( - "fmt" - "testing" - - "github.com/d5/tengo/objects" -) - -func TestString(t *testing.T) { - expect(t, `out = "Hello World!"`, nil, "Hello World!") - expect(t, `out = "Hello" + " " + "World!"`, nil, "Hello World!") - - expect(t, `out = "Hello" == "Hello"`, nil, true) - expect(t, `out = "Hello" == "World"`, nil, false) - expect(t, `out = "Hello" != "Hello"`, nil, false) - expect(t, `out = "Hello" != "World"`, nil, true) - - // index operator - str := "abcdef" - strStr := `"abcdef"` - strLen := 6 - for idx := 0; idx < strLen; idx++ { - expect(t, fmt.Sprintf("out = %s[%d]", strStr, idx), nil, str[idx]) - expect(t, fmt.Sprintf("out = %s[0 + %d]", strStr, idx), nil, str[idx]) - expect(t, fmt.Sprintf("out = %s[1 + %d - 1]", strStr, idx), nil, str[idx]) - expect(t, fmt.Sprintf("idx := %d; out = %s[idx]", idx, strStr), nil, str[idx]) - } - - expect(t, fmt.Sprintf("%s[%d]", strStr, -1), nil, objects.UndefinedValue) - expect(t, fmt.Sprintf("%s[%d]", strStr, strLen), nil, objects.UndefinedValue) - - // slice operator - for low := 0; low <= strLen; low++ { - expect(t, fmt.Sprintf("out = %s[%d:%d]", strStr, low, low), nil, "") - for high := low; high <= strLen; high++ { - expect(t, fmt.Sprintf("out = %s[%d:%d]", strStr, low, high), nil, str[low:high]) - expect(t, fmt.Sprintf("out = %s[0 + %d : 0 + %d]", strStr, low, high), nil, str[low:high]) - expect(t, fmt.Sprintf("out = %s[1 + %d - 1 : 1 + %d - 1]", strStr, low, high), nil, str[low:high]) - expect(t, fmt.Sprintf("out = %s[:%d]", strStr, high), nil, str[:high]) - expect(t, fmt.Sprintf("out = %s[%d:]", strStr, low), nil, str[low:]) - } - } - - expect(t, fmt.Sprintf("out = %s[:]", strStr), nil, str[:]) - expect(t, fmt.Sprintf("out = %s[:]", strStr), nil, str) - expect(t, fmt.Sprintf("out = %s[%d:]", strStr, -1), nil, str) - expect(t, fmt.Sprintf("out = %s[:%d]", strStr, strLen+1), nil, str) - expect(t, fmt.Sprintf("out = %s[%d:%d]", strStr, 2, 2), nil, "") - - expectError(t, fmt.Sprintf("%s[:%d]", strStr, -1), nil, "invalid slice index") - expectError(t, fmt.Sprintf("%s[%d:]", strStr, strLen+1), nil, "invalid slice index") - expectError(t, fmt.Sprintf("%s[%d:%d]", strStr, 0, -1), nil, "invalid slice index") - expectError(t, fmt.Sprintf("%s[%d:%d]", strStr, 2, 1), nil, "invalid slice index") - - // string concatenation with other types - expect(t, `out = "foo" + 1`, nil, "foo1") - // Float.String() returns the smallest number of digits - // necessary such that ParseFloat will return f exactly. - expect(t, `out = "foo" + 1.0`, nil, "foo1") // <- note '1' instead of '1.0' - expect(t, `out = "foo" + 1.5`, nil, "foo1.5") - expect(t, `out = "foo" + true`, nil, "footrue") - expect(t, `out = "foo" + 'X'`, nil, "fooX") - expect(t, `out = "foo" + error(5)`, nil, "fooerror: 5") - expect(t, `out = "foo" + undefined`, nil, "foo") - expect(t, `out = "foo" + [1,2,3]`, nil, "foo[1, 2, 3]") - // also works with "+=" operator - expect(t, `out = "foo"; out += 1.5`, nil, "foo1.5") - // string concats works only when string is LHS - expectError(t, `1 + "foo"`, nil, "invalid operation") - - expectError(t, `"foo" - "bar"`, nil, "invalid operation") -} diff --git a/runtime/vm_tail_call_test.go b/runtime/vm_tail_call_test.go deleted file mode 100644 index c310b6b..0000000 --- a/runtime/vm_tail_call_test.go +++ /dev/null @@ -1,111 +0,0 @@ -package runtime_test - -import "testing" - -func TestTailCall(t *testing.T) { - expect(t, ` - fac := func(n, a) { - if n == 1 { - return a - } - return fac(n-1, n*a) - } - out = fac(5, 1)`, nil, 120) - - expect(t, ` - fac := func(n, a) { - if n == 1 { - return a - } - x := {foo: fac} // indirection for test - return x.foo(n-1, n*a) - } - out = fac(5, 1)`, nil, 120) - - expect(t, ` - fib := func(x, s) { - if x == 0 { - return 0 + s - } else if x == 1 { - return 1 + s - } - return fib(x-1, fib(x-2, s)) - } - out = fib(15, 0)`, nil, 610) - - expect(t, ` - fib := func(n, a, b) { - if n == 0 { - return a - } else if n == 1 { - return b - } - return fib(n-1, b, a + b) - } - out = fib(15, 0, 1)`, nil, 610) - - // global variable and no return value - expect(t, ` - out = 0 - foo := func(a) { - if a == 0 { - return - } - out += a - foo(a-1) - } - foo(10)`, nil, 55) - - expect(t, ` - f1 := func() { - f2 := 0 // TODO: this might be fixed in the future - f2 = func(n, s) { - if n == 0 { return s } - return f2(n-1, n + s) - } - return f2(5, 0) - } - out = f1()`, nil, 15) - - // tail-call replacing loop - // without tail-call optimization, this code will cause stack overflow - expect(t, ` -iter := func(n, max) { - if n == max { - return n - } - - return iter(n+1, max) -} -out = iter(0, 9999) -`, nil, 9999) - expect(t, ` -c := 0 -iter := func(n, max) { - if n == max { - return - } - - c++ - iter(n+1, max) -} -iter(0, 9999) -out = c -`, nil, 9999) -} - -// tail call with free vars -func TestTailCallFreeVars(t *testing.T) { - expect(t, ` -func() { - a := 10 - f2 := 0 - f2 = func(n, s) { - if n == 0 { - return s + a - } - return f2(n-1, n+s) - } - out = f2(5, 0) -}()`, nil, 25) -} diff --git a/runtime/vm_test.go b/runtime/vm_test.go deleted file mode 100644 index eb63c9e..0000000 --- a/runtime/vm_test.go +++ /dev/null @@ -1,385 +0,0 @@ -package runtime_test - -import ( - "errors" - "fmt" - "reflect" - _runtime "runtime" - "strings" - "testing" - - "github.com/d5/tengo/assert" - "github.com/d5/tengo/compiler" - "github.com/d5/tengo/compiler/ast" - "github.com/d5/tengo/compiler/parser" - "github.com/d5/tengo/compiler/source" - "github.com/d5/tengo/objects" - "github.com/d5/tengo/runtime" - "github.com/d5/tengo/stdlib" -) - -const testOut = "out" - -type IARR []interface{} -type IMAP map[string]interface{} -type MAP = map[string]interface{} -type ARR = []interface{} - -type testopts struct { - modules *objects.ModuleMap - symbols map[string]objects.Object - maxAllocs int64 - skip2ndPass bool -} - -func Opts() *testopts { - return &testopts{ - modules: objects.NewModuleMap(), - symbols: make(map[string]objects.Object), - maxAllocs: -1, - skip2ndPass: false, - } -} - -func (o *testopts) copy() *testopts { - c := &testopts{ - modules: o.modules.Copy(), - symbols: make(map[string]objects.Object), - maxAllocs: o.maxAllocs, - skip2ndPass: o.skip2ndPass, - } - for k, v := range o.symbols { - c.symbols[k] = v - } - return c -} - -func (o *testopts) Stdlib() *testopts { - o.modules.AddMap(stdlib.GetModuleMap(stdlib.AllModuleNames()...)) - return o -} - -func (o *testopts) Module(name string, mod interface{}) *testopts { - c := o.copy() - switch mod := mod.(type) { - case objects.Importable: - c.modules.Add(name, mod) - case string: - c.modules.AddSourceModule(name, []byte(mod)) - case []byte: - c.modules.AddSourceModule(name, mod) - default: - panic(fmt.Errorf("invalid module type: %T", mod)) - } - return c -} - -func (o *testopts) Symbol(name string, value objects.Object) *testopts { - c := o.copy() - c.symbols[name] = value - return c -} - -func (o *testopts) MaxAllocs(limit int64) *testopts { - c := o.copy() - c.maxAllocs = limit - return c -} - -func (o *testopts) Skip2ndPass() *testopts { - c := o.copy() - c.skip2ndPass = true - return c -} - -func expect(t *testing.T, input string, opts *testopts, expected interface{}) { - if opts == nil { - opts = Opts() - } - - symbols := opts.symbols - modules := opts.modules - maxAllocs := opts.maxAllocs - - expectedObj := toObject(expected) - - if symbols == nil { - symbols = make(map[string]objects.Object) - } - symbols[testOut] = objectZeroCopy(expectedObj) - - // first pass: run the code normally - { - // parse - file := parse(t, input) - if file == nil { - return - } - - // compiler/VM - res, trace, err := traceCompileRun(file, symbols, modules, maxAllocs) - if !assert.NoError(t, err) || - !assert.Equal(t, expectedObj, res[testOut]) { - t.Log("\n" + strings.Join(trace, "\n")) - } - } - - // second pass: run the code as import module - if !opts.skip2ndPass { - file := parse(t, `out = import("__code__")`) - if file == nil { - return - } - - expectedObj := toObject(expected) - switch eo := expectedObj.(type) { - case *objects.Array: - expectedObj = &objects.ImmutableArray{Value: eo.Value} - case *objects.Map: - expectedObj = &objects.ImmutableMap{Value: eo.Value} - } - - modules.AddSourceModule("__code__", []byte(fmt.Sprintf("out := undefined; %s; export out", input))) - - res, trace, err := traceCompileRun(file, symbols, modules, maxAllocs) - if !assert.NoError(t, err) || - !assert.Equal(t, expectedObj, res[testOut]) { - t.Log("\n" + strings.Join(trace, "\n")) - } - } -} - -func expectError(t *testing.T, input string, opts *testopts, expected string) { - if opts == nil { - opts = Opts() - } - - symbols := opts.symbols - modules := opts.modules - maxAllocs := opts.maxAllocs - - expected = strings.TrimSpace(expected) - if expected == "" { - panic("expected must not be empty") - } - - // parse - program := parse(t, input) - if program == nil { - return - } - - // compiler/VM - _, trace, err := traceCompileRun(program, symbols, modules, maxAllocs) - if !assert.Error(t, err) || - !assert.True(t, strings.Contains(err.Error(), expected), "expected error string: %s, got: %s", expected, err.Error()) { - t.Log("\n" + strings.Join(trace, "\n")) - } -} - -type tracer struct { - Out []string -} - -func (o *tracer) Write(p []byte) (n int, err error) { - o.Out = append(o.Out, string(p)) - return len(p), nil -} - -func traceCompileRun(file *ast.File, symbols map[string]objects.Object, modules *objects.ModuleMap, maxAllocs int64) (res map[string]objects.Object, trace []string, err error) { - var v *runtime.VM - - defer func() { - if e := recover(); e != nil { - err = fmt.Errorf("panic: %v", e) - - // stack trace - var stackTrace []string - for i := 2; ; i += 1 { - _, file, line, ok := _runtime.Caller(i) - if !ok { - break - } - stackTrace = append(stackTrace, fmt.Sprintf(" %s:%d", file, line)) - } - - trace = append(trace, fmt.Sprintf("[Error Trace]\n\n %s\n", strings.Join(stackTrace, "\n "))) - } - }() - - globals := make([]objects.Object, runtime.GlobalsSize) - - symTable := compiler.NewSymbolTable() - for name, value := range symbols { - sym := symTable.Define(name) - - // should not store pointer to 'value' variable - // which is re-used in each iteration. - valueCopy := value - globals[sym.Index] = valueCopy - } - for idx, fn := range objects.Builtins { - symTable.DefineBuiltin(idx, fn.Name) - } - - tr := &tracer{} - c := compiler.NewCompiler(file.InputFile, symTable, nil, modules, tr) - err = c.Compile(file) - trace = append(trace, fmt.Sprintf("\n[Compiler Trace]\n\n%s", strings.Join(tr.Out, ""))) - if err != nil { - return - } - - bytecode := c.Bytecode() - bytecode.RemoveDuplicates() - trace = append(trace, fmt.Sprintf("\n[Compiled Constants]\n\n%s", strings.Join(bytecode.FormatConstants(), "\n"))) - trace = append(trace, fmt.Sprintf("\n[Compiled Instructions]\n\n%s\n", strings.Join(bytecode.FormatInstructions(), "\n"))) - - v = runtime.NewVM(bytecode, globals, maxAllocs) - - err = v.Run() - { - res = make(map[string]objects.Object) - for name := range symbols { - sym, depth, ok := symTable.Resolve(name) - if !ok || depth != 0 { - err = fmt.Errorf("symbol not found: %s", name) - return - } - - res[name] = globals[sym.Index] - } - trace = append(trace, fmt.Sprintf("\n[Globals]\n\n%s", strings.Join(formatGlobals(globals), "\n"))) - } - if err == nil && !v.IsStackEmpty() { - err = errors.New("non empty stack after execution") - } - - return -} - -func formatGlobals(globals []objects.Object) (formatted []string) { - for idx, global := range globals { - if global == nil { - return - } - - switch global := global.(type) { - case *objects.Closure: - formatted = append(formatted, fmt.Sprintf("[% 3d] (Closure|%p)", idx, global)) - for _, l := range compiler.FormatInstructions(global.Fn.Instructions, 0) { - formatted = append(formatted, fmt.Sprintf(" %s", l)) - } - default: - formatted = append(formatted, fmt.Sprintf("[% 3d] %s (%s|%p)", idx, global.String(), reflect.TypeOf(global).Elem().Name(), global)) - } - } - - return -} - -func parse(t *testing.T, input string) *ast.File { - testFileSet := source.NewFileSet() - testFile := testFileSet.AddFile("test", -1, len(input)) - - p := parser.NewParser(testFile, []byte(input), nil) - file, err := p.ParseFile() - if !assert.NoError(t, err) { - return nil - } - - return file -} - -func errorObject(v interface{}) *objects.Error { - return &objects.Error{Value: toObject(v)} -} - -func toObject(v interface{}) objects.Object { - switch v := v.(type) { - case objects.Object: - return v - case string: - return &objects.String{Value: v} - case int64: - return &objects.Int{Value: v} - case int: // for convenience - return &objects.Int{Value: int64(v)} - case bool: - if v { - return objects.TrueValue - } - return objects.FalseValue - case rune: - return &objects.Char{Value: v} - case byte: // for convenience - return &objects.Char{Value: rune(v)} - case float64: - return &objects.Float{Value: v} - case []byte: - return &objects.Bytes{Value: v} - case MAP: - objs := make(map[string]objects.Object) - for k, v := range v { - objs[k] = toObject(v) - } - - return &objects.Map{Value: objs} - case ARR: - var objs []objects.Object - for _, e := range v { - objs = append(objs, toObject(e)) - } - - return &objects.Array{Value: objs} - case IMAP: - objs := make(map[string]objects.Object) - for k, v := range v { - objs[k] = toObject(v) - } - - return &objects.ImmutableMap{Value: objs} - case IARR: - var objs []objects.Object - for _, e := range v { - objs = append(objs, toObject(e)) - } - - return &objects.ImmutableArray{Value: objs} - } - - panic(fmt.Errorf("unknown type: %T", v)) -} - -func objectZeroCopy(o objects.Object) objects.Object { - switch o.(type) { - case *objects.Int: - return &objects.Int{} - case *objects.Float: - return &objects.Float{} - case *objects.Bool: - return &objects.Bool{} - case *objects.Char: - return &objects.Char{} - case *objects.String: - return &objects.String{} - case *objects.Array: - return &objects.Array{} - case *objects.Map: - return &objects.Map{} - case *objects.Undefined: - return objects.UndefinedValue - case *objects.Error: - return &objects.Error{} - case *objects.Bytes: - return &objects.Bytes{} - case *objects.ImmutableArray: - return &objects.ImmutableArray{} - case *objects.ImmutableMap: - return &objects.ImmutableMap{} - case nil: - panic("nil") - default: - panic(fmt.Errorf("unknown object type: %s", o.TypeName())) - } -} diff --git a/runtime/vm_undefined_test.go b/runtime/vm_undefined_test.go deleted file mode 100644 index 6796459..0000000 --- a/runtime/vm_undefined_test.go +++ /dev/null @@ -1,21 +0,0 @@ -package runtime_test - -import ( - "testing" - - "github.com/d5/tengo/objects" -) - -func TestUndefined(t *testing.T) { - expect(t, `out = undefined`, nil, objects.UndefinedValue) - expect(t, `out = undefined.a`, nil, objects.UndefinedValue) - expect(t, `out = undefined[1]`, nil, objects.UndefinedValue) - expect(t, `out = undefined.a.b`, nil, objects.UndefinedValue) - expect(t, `out = undefined[1][2]`, nil, objects.UndefinedValue) - expect(t, `out = undefined ? 1 : 2`, nil, 2) - expect(t, `out = undefined == undefined`, nil, true) - expect(t, `out = undefined == 1`, nil, false) - expect(t, `out = 1 == undefined`, nil, false) - expect(t, `out = undefined == float([])`, nil, true) - expect(t, `out = float([]) == undefined`, nil, true) -} diff --git a/script.go b/script.go new file mode 100644 index 0000000..4d1df70 --- /dev/null +++ b/script.go @@ -0,0 +1,313 @@ +package tengo + +import ( + "context" + "fmt" + "sync" + + "github.com/d5/tengo/internal" +) + +// Script can simplify compilation and execution of embedded scripts. +type Script struct { + variables map[string]*Variable + modules *ModuleMap + input []byte + maxAllocs int64 + maxConstObjects int + enableFileImport bool +} + +// NewScript creates a Script instance with an input script. +func NewScript(input []byte) *Script { + return &Script{ + variables: make(map[string]*Variable), + input: input, + maxAllocs: -1, + maxConstObjects: -1, + } +} + +// Add adds a new variable or updates an existing variable to the script. +func (s *Script) Add(name string, value interface{}) error { + obj, err := FromInterface(value) + if err != nil { + return err + } + s.variables[name] = &Variable{ + name: name, + value: obj, + } + return nil +} + +// Remove removes (undefines) an existing variable for the script. It returns +// false if the variable name is not defined. +func (s *Script) Remove(name string) bool { + if _, ok := s.variables[name]; !ok { + return false + } + delete(s.variables, name) + return true +} + +// SetImports sets import modules. +func (s *Script) SetImports(modules *ModuleMap) { + s.modules = modules +} + +// SetMaxAllocs sets the maximum number of objects allocations during the run +// time. Compiled script will return ErrObjectAllocLimit error if it +// exceeds this limit. +func (s *Script) SetMaxAllocs(n int64) { + s.maxAllocs = n +} + +// SetMaxConstObjects sets the maximum number of objects in the compiled +// constants. +func (s *Script) SetMaxConstObjects(n int) { + s.maxConstObjects = n +} + +// EnableFileImport enables or disables module loading from local files. Local +// file modules are disabled by default. +func (s *Script) EnableFileImport(enable bool) { + s.enableFileImport = enable +} + +// Compile compiles the script with all the defined variables, and, returns +// Compiled object. +func (s *Script) Compile() (*Compiled, error) { + symbolTable, globals, err := s.prepCompile() + if err != nil { + return nil, err + } + + fileSet := internal.NewFileSet() + srcFile := fileSet.AddFile("(main)", -1, len(s.input)) + p := internal.NewParser(srcFile, s.input, nil) + file, err := p.ParseFile() + if err != nil { + return nil, err + } + + c := NewCompiler(srcFile, symbolTable, nil, s.modules, nil) + c.EnableFileImport(s.enableFileImport) + if err := c.Compile(file); err != nil { + return nil, err + } + + // reduce globals size + globals = globals[:symbolTable.MaxSymbols()+1] + + // global symbol names to indexes + globalIndexes := make(map[string]int, len(globals)) + for _, name := range symbolTable.Names() { + symbol, _, _ := symbolTable.Resolve(name) + if symbol.Scope == internal.ScopeGlobal { + globalIndexes[name] = symbol.Index + } + } + + // remove duplicates from constants + bytecode := c.Bytecode() + bytecode.RemoveDuplicates() + + // check the constant objects limit + if s.maxConstObjects >= 0 { + cnt := bytecode.CountObjects() + if cnt > s.maxConstObjects { + return nil, fmt.Errorf("exceeding constant objects limit: %d", cnt) + } + } + return &Compiled{ + globalIndexes: globalIndexes, + bytecode: bytecode, + globals: globals, + maxAllocs: s.maxAllocs, + }, nil +} + +// Run compiles and runs the scripts. Use returned compiled object to access +// global variables. +func (s *Script) Run() (compiled *Compiled, err error) { + compiled, err = s.Compile() + if err != nil { + return + } + err = compiled.Run() + return +} + +// RunContext is like Run but includes a context. +func (s *Script) RunContext( + ctx context.Context, +) (compiled *Compiled, err error) { + compiled, err = s.Compile() + if err != nil { + return + } + err = compiled.RunContext(ctx) + return +} + +func (s *Script) prepCompile() ( + symbolTable *internal.SymbolTable, + globals []Object, + err error, +) { + var names []string + for name := range s.variables { + names = append(names, name) + } + + symbolTable = internal.NewSymbolTable() + for idx, fn := range builtinFuncs { + symbolTable.DefineBuiltin(idx, fn.Name) + } + + globals = make([]Object, GlobalsSize) + + for idx, name := range names { + symbol := symbolTable.Define(name) + if symbol.Index != idx { + panic(fmt.Errorf("wrong symbol index: %d != %d", + idx, symbol.Index)) + } + globals[symbol.Index] = s.variables[name].value + } + return +} + +// Compiled is a compiled instance of the user script. Use Script.Compile() to +// create Compiled object. +type Compiled struct { + globalIndexes map[string]int // global symbol name to index + bytecode *Bytecode + globals []Object + maxAllocs int64 + lock sync.RWMutex +} + +// Run executes the compiled script in the virtual machine. +func (c *Compiled) Run() error { + c.lock.Lock() + defer c.lock.Unlock() + + v := NewVM(c.bytecode, c.globals, c.maxAllocs) + return v.Run() +} + +// RunContext is like Run but includes a context. +func (c *Compiled) RunContext(ctx context.Context) (err error) { + c.lock.Lock() + defer c.lock.Unlock() + + v := NewVM(c.bytecode, c.globals, c.maxAllocs) + ch := make(chan error, 1) + go func() { + ch <- v.Run() + }() + + select { + case <-ctx.Done(): + v.Abort() + <-ch + err = ctx.Err() + case err = <-ch: + } + return +} + +// Clone creates a new copy of Compiled. Cloned copies are safe for concurrent +// use by multiple goroutines. +func (c *Compiled) Clone() *Compiled { + c.lock.Lock() + defer c.lock.Unlock() + + clone := &Compiled{ + globalIndexes: c.globalIndexes, + bytecode: c.bytecode, + globals: make([]Object, len(c.globals)), + maxAllocs: c.maxAllocs, + } + // copy global objects + for idx, g := range c.globals { + if g != nil { + clone.globals[idx] = g + } + } + return clone +} + +// IsDefined returns true if the variable name is defined (has value) before or +// after the execution. +func (c *Compiled) IsDefined(name string) bool { + c.lock.RLock() + defer c.lock.RUnlock() + + idx, ok := c.globalIndexes[name] + if !ok { + return false + } + v := c.globals[idx] + if v == nil { + return false + } + return v != UndefinedValue +} + +// Get returns a variable identified by the name. +func (c *Compiled) Get(name string) *Variable { + c.lock.RLock() + defer c.lock.RUnlock() + + value := UndefinedValue + if idx, ok := c.globalIndexes[name]; ok { + value = c.globals[idx] + if value == nil { + value = UndefinedValue + } + } + return &Variable{ + name: name, + value: value, + } +} + +// GetAll returns all the variables that are defined by the compiled script. +func (c *Compiled) GetAll() []*Variable { + c.lock.RLock() + defer c.lock.RUnlock() + + var vars []*Variable + for name, idx := range c.globalIndexes { + value := c.globals[idx] + if value == nil { + value = UndefinedValue + } + vars = append(vars, &Variable{ + name: name, + value: value, + }) + } + return vars +} + +// Set replaces the value of a global variable identified by the name. An error +// will be returned if the name was not defined during compilation. +func (c *Compiled) Set(name string, value interface{}) error { + c.lock.Lock() + defer c.lock.Unlock() + + obj, err := FromInterface(value) + if err != nil { + return err + } + idx, ok := c.globalIndexes[name] + if !ok { + return fmt.Errorf("'%s' is not defined", name) + } + c.globals[idx] = obj + return nil +} diff --git a/script/compiled.go b/script/compiled.go deleted file mode 100644 index ce50f49..0000000 --- a/script/compiled.go +++ /dev/null @@ -1,159 +0,0 @@ -package script - -import ( - "context" - "fmt" - "sync" - - "github.com/d5/tengo/compiler" - "github.com/d5/tengo/objects" - "github.com/d5/tengo/runtime" -) - -// Compiled is a compiled instance of the user script. -// Use Script.Compile() to create Compiled object. -type Compiled struct { - globalIndexes map[string]int // global symbol name to index - bytecode *compiler.Bytecode - globals []objects.Object - maxAllocs int64 - lock sync.RWMutex -} - -// Run executes the compiled script in the virtual machine. -func (c *Compiled) Run() error { - c.lock.Lock() - defer c.lock.Unlock() - - v := runtime.NewVM(c.bytecode, c.globals, c.maxAllocs) - - return v.Run() -} - -// RunContext is like Run but includes a context. -func (c *Compiled) RunContext(ctx context.Context) (err error) { - c.lock.Lock() - defer c.lock.Unlock() - - v := runtime.NewVM(c.bytecode, c.globals, c.maxAllocs) - - ch := make(chan error, 1) - - go func() { - ch <- v.Run() - }() - - select { - case <-ctx.Done(): - v.Abort() - <-ch - err = ctx.Err() - case err = <-ch: - } - - return -} - -// Clone creates a new copy of Compiled. -// Cloned copies are safe for concurrent use by multiple goroutines. -func (c *Compiled) Clone() *Compiled { - c.lock.Lock() - defer c.lock.Unlock() - - clone := &Compiled{ - globalIndexes: c.globalIndexes, - bytecode: c.bytecode, - globals: make([]objects.Object, len(c.globals)), - maxAllocs: c.maxAllocs, - } - - // copy global objects - for idx, g := range c.globals { - if g != nil { - clone.globals[idx] = g - } - } - - return clone -} - -// IsDefined returns true if the variable name is defined (has value) before or after the execution. -func (c *Compiled) IsDefined(name string) bool { - c.lock.RLock() - defer c.lock.RUnlock() - - idx, ok := c.globalIndexes[name] - if !ok { - return false - } - - v := c.globals[idx] - if v == nil { - return false - } - - return v != objects.UndefinedValue -} - -// Get returns a variable identified by the name. -func (c *Compiled) Get(name string) *Variable { - c.lock.RLock() - defer c.lock.RUnlock() - - value := objects.UndefinedValue - - if idx, ok := c.globalIndexes[name]; ok { - value = c.globals[idx] - if value == nil { - value = objects.UndefinedValue - } - } - - return &Variable{ - name: name, - value: value, - } -} - -// GetAll returns all the variables that are defined by the compiled script. -func (c *Compiled) GetAll() []*Variable { - c.lock.RLock() - defer c.lock.RUnlock() - - var vars []*Variable - - for name, idx := range c.globalIndexes { - value := c.globals[idx] - if value == nil { - value = objects.UndefinedValue - } - - vars = append(vars, &Variable{ - name: name, - value: value, - }) - } - - return vars -} - -// Set replaces the value of a global variable identified by the name. -// An error will be returned if the name was not defined during compilation. -func (c *Compiled) Set(name string, value interface{}) error { - c.lock.Lock() - defer c.lock.Unlock() - - obj, err := objects.FromInterface(value) - if err != nil { - return err - } - - idx, ok := c.globalIndexes[name] - if !ok { - return fmt.Errorf("'%s' is not defined", name) - } - - c.globals[idx] = obj - - return nil -} diff --git a/script/compiled_test.go b/script/compiled_test.go deleted file mode 100644 index 13d2d76..0000000 --- a/script/compiled_test.go +++ /dev/null @@ -1,168 +0,0 @@ -package script_test - -import ( - "context" - "testing" - "time" - - "github.com/d5/tengo/assert" - "github.com/d5/tengo/script" -) - -type M map[string]interface{} - -func TestCompiled_Get(t *testing.T) { - // simple script - c := compile(t, `a := 5`, nil) - compiledRun(t, c) - compiledGet(t, c, "a", int64(5)) - - // user-defined variables - compileError(t, `a := b`, nil) // compile error because "b" is not defined - c = compile(t, `a := b`, M{"b": "foo"}) // now compile with b = "foo" defined - compiledGet(t, c, "a", nil) // a = undefined; because it's before Compiled.Run() - compiledRun(t, c) // Compiled.Run() - compiledGet(t, c, "a", "foo") // a = "foo" -} - -func TestCompiled_GetAll(t *testing.T) { - c := compile(t, `a := 5`, nil) - compiledRun(t, c) - compiledGetAll(t, c, M{"a": int64(5)}) - - c = compile(t, `a := b`, M{"b": "foo"}) - compiledRun(t, c) - compiledGetAll(t, c, M{"a": "foo", "b": "foo"}) - - c = compile(t, `a := b; b = 5`, M{"b": "foo"}) - compiledRun(t, c) - compiledGetAll(t, c, M{"a": "foo", "b": int64(5)}) -} - -func TestCompiled_IsDefined(t *testing.T) { - c := compile(t, `a := 5`, nil) - compiledIsDefined(t, c, "a", false) // a is not defined before Run() - compiledRun(t, c) - compiledIsDefined(t, c, "a", true) - compiledIsDefined(t, c, "b", false) -} - -func TestCompiled_Set(t *testing.T) { - c := compile(t, `a := b`, M{"b": "foo"}) - compiledRun(t, c) - compiledGet(t, c, "a", "foo") - - // replace value of 'b' - err := c.Set("b", "bar") - assert.NoError(t, err) - compiledRun(t, c) - compiledGet(t, c, "a", "bar") - - // try to replace undefined variable - err = c.Set("c", 1984) - assert.Error(t, err) // 'c' is not defined - - // case #2 - c = compile(t, ` -a := func() { - return func() { - return b + 5 - }() -}()`, M{"b": 5}) - compiledRun(t, c) - compiledGet(t, c, "a", int64(10)) - err = c.Set("b", 10) - assert.NoError(t, err) - compiledRun(t, c) - compiledGet(t, c, "a", int64(15)) -} - -func TestCompiled_RunContext(t *testing.T) { - // machine completes normally - c := compile(t, `a := 5`, nil) - err := c.RunContext(context.Background()) - assert.NoError(t, err) - compiledGet(t, c, "a", int64(5)) - - // timeout - c = compile(t, `for true {}`, nil) - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond) - defer cancel() - err = c.RunContext(ctx) - assert.Equal(t, context.DeadlineExceeded, err) -} - -func compile(t *testing.T, input string, vars M) *script.Compiled { - s := script.New([]byte(input)) - for vn, vv := range vars { - err := s.Add(vn, vv) - if !assert.NoError(t, err) { - return nil - } - } - - c, err := s.Compile() - if !assert.NoError(t, err) || !assert.NotNil(t, c) { - return nil - } - - return c -} - -func compileError(t *testing.T, input string, vars M) bool { - s := script.New([]byte(input)) - for vn, vv := range vars { - err := s.Add(vn, vv) - if !assert.NoError(t, err) { - return false - } - } - - _, err := s.Compile() - - return assert.Error(t, err) -} - -func compiledRun(t *testing.T, c *script.Compiled) bool { - err := c.Run() - - return assert.NoError(t, err) -} - -func compiledGet(t *testing.T, c *script.Compiled, name string, expected interface{}) bool { - v := c.Get(name) - if !assert.NotNil(t, v) { - return false - } - - return assert.Equal(t, expected, v.Value()) -} - -func compiledGetAll(t *testing.T, c *script.Compiled, expected M) bool { - vars := c.GetAll() - - if !assert.Equal(t, len(expected), len(vars)) { - return false - } - - for k, v := range expected { - var found bool - for _, e := range vars { - if e.Name() == k { - if !assert.Equal(t, v, e.Value()) { - return false - } - found = true - } - } - if !found { - assert.Fail(t, "variable '%s' not found", k) - } - } - - return true -} - -func compiledIsDefined(t *testing.T, c *script.Compiled, name string, expected bool) bool { - return assert.Equal(t, expected, c.IsDefined(name)) -} diff --git a/script/script.go b/script/script.go deleted file mode 100644 index 2ee67b6..0000000 --- a/script/script.go +++ /dev/null @@ -1,185 +0,0 @@ -package script - -import ( - "context" - "fmt" - - "github.com/d5/tengo/compiler" - "github.com/d5/tengo/compiler/parser" - "github.com/d5/tengo/compiler/source" - "github.com/d5/tengo/objects" - "github.com/d5/tengo/runtime" -) - -// Script can simplify compilation and execution of embedded scripts. -type Script struct { - variables map[string]*Variable - modules *objects.ModuleMap - input []byte - maxAllocs int64 - maxConstObjects int - enableFileImport bool -} - -// New creates a Script instance with an input script. -func New(input []byte) *Script { - return &Script{ - variables: make(map[string]*Variable), - input: input, - maxAllocs: -1, - maxConstObjects: -1, - } -} - -// Add adds a new variable or updates an existing variable to the script. -func (s *Script) Add(name string, value interface{}) error { - obj, err := objects.FromInterface(value) - if err != nil { - return err - } - - s.variables[name] = &Variable{ - name: name, - value: obj, - } - - return nil -} - -// Remove removes (undefines) an existing variable for the script. -// It returns false if the variable name is not defined. -func (s *Script) Remove(name string) bool { - if _, ok := s.variables[name]; !ok { - return false - } - - delete(s.variables, name) - - return true -} - -// SetImports sets import modules. -func (s *Script) SetImports(modules *objects.ModuleMap) { - s.modules = modules -} - -// SetMaxAllocs sets the maximum number of objects allocations during the run time. -// Compiled script will return runtime.ErrObjectAllocLimit error if it exceeds this limit. -func (s *Script) SetMaxAllocs(n int64) { - s.maxAllocs = n -} - -// SetMaxConstObjects sets the maximum number of objects in the compiled constants. -func (s *Script) SetMaxConstObjects(n int) { - s.maxConstObjects = n -} - -// EnableFileImport enables or disables module loading from local files. -// Local file modules are disabled by default. -func (s *Script) EnableFileImport(enable bool) { - s.enableFileImport = enable -} - -// Compile compiles the script with all the defined variables, and, returns Compiled object. -func (s *Script) Compile() (*Compiled, error) { - symbolTable, globals, err := s.prepCompile() - if err != nil { - return nil, err - } - - fileSet := source.NewFileSet() - srcFile := fileSet.AddFile("(main)", -1, len(s.input)) - - p := parser.NewParser(srcFile, s.input, nil) - file, err := p.ParseFile() - if err != nil { - return nil, err - } - - c := compiler.NewCompiler(srcFile, symbolTable, nil, s.modules, nil) - c.EnableFileImport(s.enableFileImport) - if err := c.Compile(file); err != nil { - return nil, err - } - - // reduce globals size - globals = globals[:symbolTable.MaxSymbols()+1] - - // global symbol names to indexes - globalIndexes := make(map[string]int, len(globals)) - for _, name := range symbolTable.Names() { - symbol, _, _ := symbolTable.Resolve(name) - if symbol.Scope == compiler.ScopeGlobal { - globalIndexes[name] = symbol.Index - } - } - - // remove duplicates from constants - bytecode := c.Bytecode() - bytecode.RemoveDuplicates() - - // check the constant objects limit - if s.maxConstObjects >= 0 { - cnt := bytecode.CountObjects() - if cnt > s.maxConstObjects { - return nil, fmt.Errorf("exceeding constant objects limit: %d", cnt) - } - } - - return &Compiled{ - globalIndexes: globalIndexes, - bytecode: bytecode, - globals: globals, - maxAllocs: s.maxAllocs, - }, nil -} - -// Run compiles and runs the scripts. -// Use returned compiled object to access global variables. -func (s *Script) Run() (compiled *Compiled, err error) { - compiled, err = s.Compile() - if err != nil { - return - } - - err = compiled.Run() - - return -} - -// RunContext is like Run but includes a context. -func (s *Script) RunContext(ctx context.Context) (compiled *Compiled, err error) { - compiled, err = s.Compile() - if err != nil { - return - } - - err = compiled.RunContext(ctx) - - return -} - -func (s *Script) prepCompile() (symbolTable *compiler.SymbolTable, globals []objects.Object, err error) { - var names []string - for name := range s.variables { - names = append(names, name) - } - - symbolTable = compiler.NewSymbolTable() - for idx, fn := range objects.Builtins { - symbolTable.DefineBuiltin(idx, fn.Name) - } - - globals = make([]objects.Object, runtime.GlobalsSize) - - for idx, name := range names { - symbol := symbolTable.Define(name) - if symbol.Index != idx { - panic(fmt.Errorf("wrong symbol index: %d != %d", idx, symbol.Index)) - } - - globals[symbol.Index] = s.variables[name].value - } - - return -} diff --git a/script/script_concurrency_test.go b/script/script_concurrency_test.go deleted file mode 100644 index 6c800c4..0000000 --- a/script/script_concurrency_test.go +++ /dev/null @@ -1,99 +0,0 @@ -package script_test - -import ( - "math/rand" - "sync" - "testing" - "time" - - "github.com/d5/tengo/assert" - "github.com/d5/tengo/objects" - "github.com/d5/tengo/script" -) - -func TestScriptConcurrency(t *testing.T) { - solve := func(a, b, c int) (d, e int) { - a += 2 - b += c - a += b * 2 - d = a + b + c - e = 0 - for i := 1; i <= d; i++ { - e += i - } - e *= 2 - return - } - - code := []byte(` -mod1 := import("mod1") - -a += 2 -b += c -a += b * 2 - -arr := [a, b, c] -arrstr := string(arr) -map := {a: a, b: b, c: c} - -d := a + b + c -s := 0 - -for i:=1; i<=d; i++ { - s += i -} - -e := mod1.double(s) -`) - mod1 := map[string]objects.Object{ - "double": &objects.UserFunction{ - Value: func(args ...objects.Object) (ret objects.Object, err error) { - arg0, _ := objects.ToInt64(args[0]) - ret = &objects.Int{Value: arg0 * 2} - return - }, - }, - } - - scr := script.New(code) - _ = scr.Add("a", 0) - _ = scr.Add("b", 0) - _ = scr.Add("c", 0) - mods := objects.NewModuleMap() - mods.AddBuiltinModule("mod1", mod1) - scr.SetImports(mods) - compiled, err := scr.Compile() - assert.NoError(t, err) - - executeFn := func(compiled *script.Compiled, a, b, c int) (d, e int) { - _ = compiled.Set("a", a) - _ = compiled.Set("b", b) - _ = compiled.Set("c", c) - err := compiled.Run() - assert.NoError(t, err) - d = compiled.Get("d").Int() - e = compiled.Get("e").Int() - return - } - - concurrency := 500 - var wg sync.WaitGroup - wg.Add(concurrency) - for i := 0; i < concurrency; i++ { - go func(compiled *script.Compiled) { - time.Sleep(time.Duration(rand.Int63n(50)) * time.Millisecond) - defer wg.Done() - - a := rand.Intn(10) - b := rand.Intn(10) - c := rand.Intn(10) - - d, e := executeFn(compiled, a, b, c) - expectedD, expectedE := solve(a, b, c) - - assert.Equal(t, expectedD, d, "input: %d, %d, %d", a, b, c) - assert.Equal(t, expectedE, e, "input: %d, %d, %d", a, b, c) - }(compiled.Clone()) - } - wg.Wait() -} diff --git a/script/script_custom_test.go b/script/script_custom_test.go deleted file mode 100644 index d8b0a9f..0000000 --- a/script/script_custom_test.go +++ /dev/null @@ -1,101 +0,0 @@ -package script_test - -import ( - "errors" - "fmt" - "testing" - - "github.com/d5/tengo/assert" - "github.com/d5/tengo/compiler/token" - "github.com/d5/tengo/objects" - "github.com/d5/tengo/script" -) - -type Counter struct { - value int64 -} - -func (o *Counter) TypeName() string { - return "counter" -} - -func (o *Counter) String() string { - return fmt.Sprintf("Counter(%d)", o.value) -} - -func (o *Counter) BinaryOp(op token.Token, rhs objects.Object) (objects.Object, error) { - switch rhs := rhs.(type) { - case *Counter: - switch op { - case token.Add: - return &Counter{value: o.value + rhs.value}, nil - case token.Sub: - return &Counter{value: o.value - rhs.value}, nil - } - case *objects.Int: - switch op { - case token.Add: - return &Counter{value: o.value + rhs.Value}, nil - case token.Sub: - return &Counter{value: o.value - rhs.Value}, nil - } - } - - return nil, errors.New("invalid operator") -} - -func (o *Counter) IsFalsy() bool { - return o.value == 0 -} - -func (o *Counter) Equals(t objects.Object) bool { - if tc, ok := t.(*Counter); ok { - return o.value == tc.value - } - - return false -} - -func (o *Counter) Copy() objects.Object { - return &Counter{value: o.value} -} - -func (o *Counter) Call(args ...objects.Object) (objects.Object, error) { - return &objects.Int{Value: o.value}, nil -} - -func TestScript_CustomObjects(t *testing.T) { - c := compile(t, `a := c1(); s := string(c1); c2 := c1; c2++`, M{ - "c1": &Counter{value: 5}, - }) - compiledRun(t, c) - compiledGet(t, c, "a", int64(5)) - compiledGet(t, c, "s", "Counter(5)") - compiledGetCounter(t, c, "c2", &Counter{value: 6}) - - c = compile(t, ` -arr := [1, 2, 3, 4] -for x in arr { - c1 += x -} -out := c1() -`, M{ - "c1": &Counter{value: 5}, - }) - compiledRun(t, c) - compiledGet(t, c, "out", int64(15)) -} - -func compiledGetCounter(t *testing.T, c *script.Compiled, name string, expected *Counter) bool { - v := c.Get(name) - if !assert.NotNil(t, v) { - return false - } - - actual := v.Value().(*Counter) - if !assert.NotNil(t, actual) { - return false - } - - return assert.Equal(t, expected.value, actual.value) -} diff --git a/script/script_module_test.go b/script/script_module_test.go deleted file mode 100644 index bb06198..0000000 --- a/script/script_module_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package script_test - -import ( - "strings" - "testing" - - "github.com/d5/tengo/assert" - "github.com/d5/tengo/objects" - "github.com/d5/tengo/script" -) - -func TestScriptSourceModule(t *testing.T) { - // script1 imports "mod1" - scr := script.New([]byte(`out := import("mod")`)) - mods := objects.NewModuleMap() - mods.AddSourceModule("mod", []byte(`export 5`)) - scr.SetImports(mods) - c, err := scr.Run() - if !assert.NoError(t, err) { - return - } - assert.Equal(t, int64(5), c.Get("out").Value()) - - // executing module function - scr = script.New([]byte(`fn := import("mod"); out := fn()`)) - mods = objects.NewModuleMap() - mods.AddSourceModule("mod", []byte(`a := 3; export func() { return a + 5 }`)) - scr.SetImports(mods) - c, err = scr.Run() - if !assert.NoError(t, err) { - return - } - assert.Equal(t, int64(8), c.Get("out").Value()) - - scr = script.New([]byte(`out := import("mod")`)) - mods = objects.NewModuleMap() - mods.AddSourceModule("mod", []byte(`text := import("text"); export text.title("foo")`)) - mods.AddBuiltinModule("text", map[string]objects.Object{ - "title": &objects.UserFunction{Name: "title", Value: func(args ...objects.Object) (ret objects.Object, err error) { - s, _ := objects.ToString(args[0]) - return &objects.String{Value: strings.Title(s)}, nil - }}, - }) - scr.SetImports(mods) - c, err = scr.Run() - if !assert.NoError(t, err) { - return - } - assert.Equal(t, "Foo", c.Get("out").Value()) - scr.SetImports(nil) - _, err = scr.Run() - if !assert.Error(t, err) { - return - } -} diff --git a/script/script_test.go b/script/script_test.go deleted file mode 100644 index 9f32ace..0000000 --- a/script/script_test.go +++ /dev/null @@ -1,126 +0,0 @@ -package script_test - -import ( - "testing" - - "github.com/d5/tengo/assert" - "github.com/d5/tengo/objects" - "github.com/d5/tengo/script" - "github.com/d5/tengo/stdlib" -) - -func TestScript_Add(t *testing.T) { - s := script.New([]byte(`a := b; c := test(b); d := test(5)`)) - assert.NoError(t, s.Add("b", 5)) // b = 5 - assert.NoError(t, s.Add("b", "foo")) // b = "foo" (re-define before compilation) - assert.NoError(t, s.Add("test", func(args ...objects.Object) (ret objects.Object, err error) { - if len(args) > 0 { - switch arg := args[0].(type) { - case *objects.Int: - return &objects.Int{Value: arg.Value + 1}, nil - } - } - - return &objects.Int{Value: 0}, nil - })) - c, err := s.Compile() - assert.NoError(t, err) - assert.NoError(t, c.Run()) - assert.Equal(t, "foo", c.Get("a").Value()) - assert.Equal(t, "foo", c.Get("b").Value()) - assert.Equal(t, int64(0), c.Get("c").Value()) - assert.Equal(t, int64(6), c.Get("d").Value()) -} - -func TestScript_Remove(t *testing.T) { - s := script.New([]byte(`a := b`)) - err := s.Add("b", 5) - assert.NoError(t, err) - assert.True(t, s.Remove("b")) // b is removed - _, err = s.Compile() // should not compile because b is undefined - assert.Error(t, err) -} - -func TestScript_Run(t *testing.T) { - s := script.New([]byte(`a := b`)) - err := s.Add("b", 5) - assert.NoError(t, err) - c, err := s.Run() - assert.NoError(t, err) - assert.NotNil(t, c) - compiledGet(t, c, "a", int64(5)) -} - -func TestScript_BuiltinModules(t *testing.T) { - s := script.New([]byte(`math := import("math"); a := math.abs(-19.84)`)) - s.SetImports(stdlib.GetModuleMap("math")) - c, err := s.Run() - assert.NoError(t, err) - assert.NotNil(t, c) - compiledGet(t, c, "a", 19.84) - - c, err = s.Run() - assert.NoError(t, err) - assert.NotNil(t, c) - compiledGet(t, c, "a", 19.84) - - s.SetImports(stdlib.GetModuleMap("os")) - _, err = s.Run() - assert.Error(t, err) - - s.SetImports(nil) - _, err = s.Run() - assert.Error(t, err) -} - -func TestScript_SourceModules(t *testing.T) { - s := script.New([]byte(` -enum := import("enum") -a := enum.all([1,2,3], func(_, v) { - return v > 0 -}) -`)) - s.SetImports(stdlib.GetModuleMap("enum")) - c, err := s.Run() - assert.NoError(t, err) - assert.NotNil(t, c) - compiledGet(t, c, "a", true) - - s.SetImports(nil) - _, err = s.Run() - assert.Error(t, err) -} - -func TestScript_SetMaxConstObjects(t *testing.T) { - // one constant '5' - s := script.New([]byte(`a := 5`)) - s.SetMaxConstObjects(1) // limit = 1 - _, err := s.Compile() - assert.NoError(t, err) - s.SetMaxConstObjects(0) // limit = 0 - _, err = s.Compile() - assert.Equal(t, "exceeding constant objects limit: 1", err.Error()) - - // two constants '5' and '1' - s = script.New([]byte(`a := 5 + 1`)) - s.SetMaxConstObjects(2) // limit = 2 - _, err = s.Compile() - assert.NoError(t, err) - s.SetMaxConstObjects(1) // limit = 1 - _, err = s.Compile() - assert.Equal(t, "exceeding constant objects limit: 2", err.Error()) - - // duplicates will be removed - s = script.New([]byte(`a := 5 + 5`)) - s.SetMaxConstObjects(1) // limit = 1 - _, err = s.Compile() - assert.NoError(t, err) - s.SetMaxConstObjects(0) // limit = 0 - _, err = s.Compile() - assert.Equal(t, "exceeding constant objects limit: 1", err.Error()) - - // no limit set - s = script.New([]byte(`a := 1 + 2 + 3 + 4 + 5`)) - _, err = s.Compile() - assert.NoError(t, err) -} diff --git a/script/variable_test.go b/script/variable_test.go deleted file mode 100644 index 8f67c32..0000000 --- a/script/variable_test.go +++ /dev/null @@ -1,82 +0,0 @@ -package script_test - -import ( - "testing" - - "github.com/d5/tengo/assert" - "github.com/d5/tengo/objects" - "github.com/d5/tengo/script" -) - -type VariableTest struct { - Name string - Value interface{} - ValueType string - IntValue int - Int64Value int64 - FloatValue float64 - CharValue rune - BoolValue bool - StringValue string - Object objects.Object - IsUndefined bool -} - -func TestVariable(t *testing.T) { - vars := []VariableTest{ - { - Name: "a", - Value: int64(1), - ValueType: "int", - IntValue: 1, - Int64Value: 1, - FloatValue: 1.0, - CharValue: rune(1), - BoolValue: true, - StringValue: "1", - Object: &objects.Int{Value: 1}, - }, - { - Name: "b", - Value: "52.11", - ValueType: "string", - FloatValue: 52.11, - StringValue: "52.11", - BoolValue: true, - Object: &objects.String{Value: "52.11"}, - }, - { - Name: "c", - Value: true, - ValueType: "bool", - IntValue: 1, - Int64Value: 1, - FloatValue: 0, - BoolValue: true, - StringValue: "true", - Object: objects.TrueValue, - }, - { - Name: "d", - Value: nil, - ValueType: "undefined", - Object: objects.UndefinedValue, - IsUndefined: true, - }, - } - - for _, tc := range vars { - v, err := script.NewVariable(tc.Name, tc.Value) - assert.NoError(t, err) - assert.Equal(t, tc.Value, v.Value(), "Name: %s", tc.Name) - assert.Equal(t, tc.ValueType, v.ValueType(), "Name: %s", tc.Name) - assert.Equal(t, tc.IntValue, v.Int(), "Name: %s", tc.Name) - assert.Equal(t, tc.Int64Value, v.Int64(), "Name: %s", tc.Name) - assert.Equal(t, tc.FloatValue, v.Float(), "Name: %s", tc.Name) - assert.Equal(t, tc.CharValue, v.Char(), "Name: %s", tc.Name) - assert.Equal(t, tc.BoolValue, v.Bool(), "Name: %s", tc.Name) - assert.Equal(t, tc.StringValue, v.String(), "Name: %s", tc.Name) - assert.Equal(t, tc.Object, v.Object(), "Name: %s", tc.Name) - assert.Equal(t, tc.IsUndefined, v.IsUndefined(), "Name: %s", tc.Name) - } -} diff --git a/script_test.go b/script_test.go new file mode 100644 index 0000000..b1b5ba6 --- /dev/null +++ b/script_test.go @@ -0,0 +1,548 @@ +package tengo_test + +import ( + "context" + "errors" + "fmt" + "math/rand" + "strings" + "sync" + "testing" + "time" + + "github.com/d5/tengo" + "github.com/d5/tengo/internal/require" + "github.com/d5/tengo/internal/token" + "github.com/d5/tengo/stdlib" +) + +func TestScript_Add(t *testing.T) { + s := tengo.NewScript([]byte(`a := b; c := test(b); d := test(5)`)) + require.NoError(t, s.Add("b", 5)) // b = 5 + require.NoError(t, s.Add("b", "foo")) // b = "foo" (re-define before compilation) + require.NoError(t, s.Add("test", + func(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) > 0 { + switch arg := args[0].(type) { + case *tengo.Int: + return &tengo.Int{Value: arg.Value + 1}, nil + } + } + + return &tengo.Int{Value: 0}, nil + })) + c, err := s.Compile() + require.NoError(t, err) + require.NoError(t, c.Run()) + require.Equal(t, "foo", c.Get("a").Value()) + require.Equal(t, "foo", c.Get("b").Value()) + require.Equal(t, int64(0), c.Get("c").Value()) + require.Equal(t, int64(6), c.Get("d").Value()) +} + +func TestScript_Remove(t *testing.T) { + s := tengo.NewScript([]byte(`a := b`)) + err := s.Add("b", 5) + require.NoError(t, err) + require.True(t, s.Remove("b")) // b is removed + _, err = s.Compile() // should not compile because b is undefined + require.Error(t, err) +} + +func TestScript_Run(t *testing.T) { + s := tengo.NewScript([]byte(`a := b`)) + err := s.Add("b", 5) + require.NoError(t, err) + c, err := s.Run() + require.NoError(t, err) + require.NotNil(t, c) + compiledGet(t, c, "a", int64(5)) +} + +func TestScript_BuiltinModules(t *testing.T) { + s := tengo.NewScript([]byte(`math := import("math"); a := math.abs(-19.84)`)) + s.SetImports(stdlib.GetModuleMap("math")) + c, err := s.Run() + require.NoError(t, err) + require.NotNil(t, c) + compiledGet(t, c, "a", 19.84) + + c, err = s.Run() + require.NoError(t, err) + require.NotNil(t, c) + compiledGet(t, c, "a", 19.84) + + s.SetImports(stdlib.GetModuleMap("os")) + _, err = s.Run() + require.Error(t, err) + + s.SetImports(nil) + _, err = s.Run() + require.Error(t, err) +} + +func TestScript_SourceModules(t *testing.T) { + s := tengo.NewScript([]byte(` +enum := import("enum") +a := enum.all([1,2,3], func(_, v) { + return v > 0 +}) +`)) + s.SetImports(stdlib.GetModuleMap("enum")) + c, err := s.Run() + require.NoError(t, err) + require.NotNil(t, c) + compiledGet(t, c, "a", true) + + s.SetImports(nil) + _, err = s.Run() + require.Error(t, err) +} + +func TestScript_SetMaxConstObjects(t *testing.T) { + // one constant '5' + s := tengo.NewScript([]byte(`a := 5`)) + s.SetMaxConstObjects(1) // limit = 1 + _, err := s.Compile() + require.NoError(t, err) + s.SetMaxConstObjects(0) // limit = 0 + _, err = s.Compile() + require.Error(t, err) + require.Equal(t, "exceeding constant objects limit: 1", err.Error()) + + // two constants '5' and '1' + s = tengo.NewScript([]byte(`a := 5 + 1`)) + s.SetMaxConstObjects(2) // limit = 2 + _, err = s.Compile() + require.NoError(t, err) + s.SetMaxConstObjects(1) // limit = 1 + _, err = s.Compile() + require.Error(t, err) + require.Equal(t, "exceeding constant objects limit: 2", err.Error()) + + // duplicates will be removed + s = tengo.NewScript([]byte(`a := 5 + 5`)) + s.SetMaxConstObjects(1) // limit = 1 + _, err = s.Compile() + require.NoError(t, err) + s.SetMaxConstObjects(0) // limit = 0 + _, err = s.Compile() + require.Error(t, err) + require.Equal(t, "exceeding constant objects limit: 1", err.Error()) + + // no limit set + s = tengo.NewScript([]byte(`a := 1 + 2 + 3 + 4 + 5`)) + _, err = s.Compile() + require.NoError(t, err) +} + +func TestScriptConcurrency(t *testing.T) { + solve := func(a, b, c int) (d, e int) { + a += 2 + b += c + a += b * 2 + d = a + b + c + e = 0 + for i := 1; i <= d; i++ { + e += i + } + e *= 2 + return + } + + code := []byte(` +mod1 := import("mod1") + +a += 2 +b += c +a += b * 2 + +arr := [a, b, c] +arrstr := string(arr) +map := {a: a, b: b, c: c} + +d := a + b + c +s := 0 + +for i:=1; i<=d; i++ { + s += i +} + +e := mod1.double(s) +`) + mod1 := map[string]tengo.Object{ + "double": &tengo.UserFunction{ + Value: func(args ...tengo.Object) ( + ret tengo.Object, + err error, + ) { + arg0, _ := tengo.ToInt64(args[0]) + ret = &tengo.Int{Value: arg0 * 2} + return + }, + }, + } + + scr := tengo.NewScript(code) + _ = scr.Add("a", 0) + _ = scr.Add("b", 0) + _ = scr.Add("c", 0) + mods := tengo.NewModuleMap() + mods.AddBuiltinModule("mod1", mod1) + scr.SetImports(mods) + compiled, err := scr.Compile() + require.NoError(t, err) + + executeFn := func(compiled *tengo.Compiled, a, b, c int) (d, e int) { + _ = compiled.Set("a", a) + _ = compiled.Set("b", b) + _ = compiled.Set("c", c) + err := compiled.Run() + require.NoError(t, err) + d = compiled.Get("d").Int() + e = compiled.Get("e").Int() + return + } + + concurrency := 500 + var wg sync.WaitGroup + wg.Add(concurrency) + for i := 0; i < concurrency; i++ { + go func(compiled *tengo.Compiled) { + time.Sleep(time.Duration(rand.Int63n(50)) * time.Millisecond) + defer wg.Done() + + a := rand.Intn(10) + b := rand.Intn(10) + c := rand.Intn(10) + + d, e := executeFn(compiled, a, b, c) + expectedD, expectedE := solve(a, b, c) + + require.Equal(t, expectedD, d, "input: %d, %d, %d", a, b, c) + require.Equal(t, expectedE, e, "input: %d, %d, %d", a, b, c) + }(compiled.Clone()) + } + wg.Wait() +} + +type Counter struct { + tengo.ObjectImpl + value int64 +} + +func (o *Counter) TypeName() string { + return "counter" +} + +func (o *Counter) String() string { + return fmt.Sprintf("Counter(%d)", o.value) +} + +func (o *Counter) BinaryOp( + op token.Token, + rhs tengo.Object, +) (tengo.Object, error) { + switch rhs := rhs.(type) { + case *Counter: + switch op { + case token.Add: + return &Counter{value: o.value + rhs.value}, nil + case token.Sub: + return &Counter{value: o.value - rhs.value}, nil + } + case *tengo.Int: + switch op { + case token.Add: + return &Counter{value: o.value + rhs.Value}, nil + case token.Sub: + return &Counter{value: o.value - rhs.Value}, nil + } + } + + return nil, errors.New("invalid operator") +} + +func (o *Counter) IsFalsy() bool { + return o.value == 0 +} + +func (o *Counter) Equals(t tengo.Object) bool { + if tc, ok := t.(*Counter); ok { + return o.value == tc.value + } + + return false +} + +func (o *Counter) Copy() tengo.Object { + return &Counter{value: o.value} +} + +func (o *Counter) Call(_ ...tengo.Object) (tengo.Object, error) { + return &tengo.Int{Value: o.value}, nil +} + +func (o *Counter) CanCall() bool { + return true +} + +func TestScript_CustomObjects(t *testing.T) { + c := compile(t, `a := c1(); s := string(c1); c2 := c1; c2++`, M{ + "c1": &Counter{value: 5}, + }) + compiledRun(t, c) + compiledGet(t, c, "a", int64(5)) + compiledGet(t, c, "s", "Counter(5)") + compiledGetCounter(t, c, "c2", &Counter{value: 6}) + + c = compile(t, ` +arr := [1, 2, 3, 4] +for x in arr { + c1 += x +} +out := c1() +`, M{ + "c1": &Counter{value: 5}, + }) + compiledRun(t, c) + compiledGet(t, c, "out", int64(15)) +} + +func compiledGetCounter( + t *testing.T, + c *tengo.Compiled, + name string, + expected *Counter, +) { + v := c.Get(name) + require.NotNil(t, v) + + actual := v.Value().(*Counter) + require.NotNil(t, actual) + require.Equal(t, expected.value, actual.value) +} + +func TestScriptSourceModule(t *testing.T) { + // script1 imports "mod1" + scr := tengo.NewScript([]byte(`out := import("mod")`)) + mods := tengo.NewModuleMap() + mods.AddSourceModule("mod", []byte(`export 5`)) + scr.SetImports(mods) + c, err := scr.Run() + require.NoError(t, err) + require.Equal(t, int64(5), c.Get("out").Value()) + + // executing module function + scr = tengo.NewScript([]byte(`fn := import("mod"); out := fn()`)) + mods = tengo.NewModuleMap() + mods.AddSourceModule("mod", + []byte(`a := 3; export func() { return a + 5 }`)) + scr.SetImports(mods) + c, err = scr.Run() + require.NoError(t, err) + require.Equal(t, int64(8), c.Get("out").Value()) + + scr = tengo.NewScript([]byte(`out := import("mod")`)) + mods = tengo.NewModuleMap() + mods.AddSourceModule("mod", + []byte(`text := import("text"); export text.title("foo")`)) + mods.AddBuiltinModule("text", + map[string]tengo.Object{ + "title": &tengo.UserFunction{ + Name: "title", + Value: func(args ...tengo.Object) (tengo.Object, error) { + s, _ := tengo.ToString(args[0]) + return &tengo.String{Value: strings.Title(s)}, nil + }}, + }) + scr.SetImports(mods) + c, err = scr.Run() + require.NoError(t, err) + require.Equal(t, "Foo", c.Get("out").Value()) + scr.SetImports(nil) + _, err = scr.Run() + require.Error(t, err) +} + +func BenchmarkArrayIndex(b *testing.B) { + bench(b.N, `a := [1, 2, 3, 4, 5, 6, 7, 8, 9]; + for i := 0; i < 1000; i++ { + a[0]; a[1]; a[2]; a[3]; a[4]; a[5]; a[6]; a[7]; a[7]; + } + `) +} + +func BenchmarkArrayIndexCompare(b *testing.B) { + bench(b.N, `a := [1, 2, 3, 4, 5, 6, 7, 8, 9]; + for i := 0; i < 1000; i++ { + 1; 2; 3; 4; 5; 6; 7; 8; 9; + } + `) +} + +func bench(n int, input string) { + s := tengo.NewScript([]byte(input)) + c, err := s.Compile() + if err != nil { + panic(err) + } + + for i := 0; i < n; i++ { + if err := c.Run(); err != nil { + panic(err) + } + } +} + +type M map[string]interface{} + +func TestCompiled_Get(t *testing.T) { + // simple script + c := compile(t, `a := 5`, nil) + compiledRun(t, c) + compiledGet(t, c, "a", int64(5)) + + // user-defined variables + compileError(t, `a := b`, nil) // compile error because "b" is not defined + c = compile(t, `a := b`, M{"b": "foo"}) // now compile with b = "foo" defined + compiledGet(t, c, "a", nil) // a = undefined; because it's before Compiled.Run() + compiledRun(t, c) // Compiled.Run() + compiledGet(t, c, "a", "foo") // a = "foo" +} + +func TestCompiled_GetAll(t *testing.T) { + c := compile(t, `a := 5`, nil) + compiledRun(t, c) + compiledGetAll(t, c, M{"a": int64(5)}) + + c = compile(t, `a := b`, M{"b": "foo"}) + compiledRun(t, c) + compiledGetAll(t, c, M{"a": "foo", "b": "foo"}) + + c = compile(t, `a := b; b = 5`, M{"b": "foo"}) + compiledRun(t, c) + compiledGetAll(t, c, M{"a": "foo", "b": int64(5)}) +} + +func TestCompiled_IsDefined(t *testing.T) { + c := compile(t, `a := 5`, nil) + compiledIsDefined(t, c, "a", false) // a is not defined before Run() + compiledRun(t, c) + compiledIsDefined(t, c, "a", true) + compiledIsDefined(t, c, "b", false) +} + +func TestCompiled_Set(t *testing.T) { + c := compile(t, `a := b`, M{"b": "foo"}) + compiledRun(t, c) + compiledGet(t, c, "a", "foo") + + // replace value of 'b' + err := c.Set("b", "bar") + require.NoError(t, err) + compiledRun(t, c) + compiledGet(t, c, "a", "bar") + + // try to replace undefined variable + err = c.Set("c", 1984) + require.Error(t, err) // 'c' is not defined + + // case #2 + c = compile(t, ` +a := func() { + return func() { + return b + 5 + }() +}()`, M{"b": 5}) + compiledRun(t, c) + compiledGet(t, c, "a", int64(10)) + err = c.Set("b", 10) + require.NoError(t, err) + compiledRun(t, c) + compiledGet(t, c, "a", int64(15)) +} + +func TestCompiled_RunContext(t *testing.T) { + // machine completes normally + c := compile(t, `a := 5`, nil) + err := c.RunContext(context.Background()) + require.NoError(t, err) + compiledGet(t, c, "a", int64(5)) + + // timeout + c = compile(t, `for true {}`, nil) + ctx, cancel := context.WithTimeout(context.Background(), + 1*time.Millisecond) + defer cancel() + err = c.RunContext(ctx) + require.Equal(t, context.DeadlineExceeded, err) +} + +func compile(t *testing.T, input string, vars M) *tengo.Compiled { + s := tengo.NewScript([]byte(input)) + for vn, vv := range vars { + err := s.Add(vn, vv) + require.NoError(t, err) + } + + c, err := s.Compile() + require.NoError(t, err) + require.NotNil(t, c) + return c +} + +func compileError(t *testing.T, input string, vars M) { + s := tengo.NewScript([]byte(input)) + for vn, vv := range vars { + err := s.Add(vn, vv) + require.NoError(t, err) + } + _, err := s.Compile() + require.Error(t, err) +} + +func compiledRun(t *testing.T, c *tengo.Compiled) { + err := c.Run() + require.NoError(t, err) +} + +func compiledGet( + t *testing.T, + c *tengo.Compiled, + name string, + expected interface{}, +) { + v := c.Get(name) + require.NotNil(t, v) + require.Equal(t, expected, v.Value()) +} + +func compiledGetAll( + t *testing.T, + c *tengo.Compiled, + expected M, +) { + vars := c.GetAll() + require.Equal(t, len(expected), len(vars)) + + for k, v := range expected { + var found bool + for _, e := range vars { + if e.Name() == k { + require.Equal(t, v, e.Value()) + found = true + } + } + require.True(t, found, "variable '%s' not found", k) + } +} + +func compiledIsDefined( + t *testing.T, + c *tengo.Compiled, + name string, + expected bool, +) { + require.Equal(t, expected, c.IsDefined(name)) +} diff --git a/stdlib/base64.go b/stdlib/base64.go index 40a746c..6a34066 100644 --- a/stdlib/base64.go +++ b/stdlib/base64.go @@ -2,19 +2,33 @@ package stdlib import ( "encoding/base64" - "github.com/d5/tengo/objects" + + "github.com/d5/tengo" ) -var base64Module = map[string]objects.Object{ - "encode": &objects.UserFunction{Value: FuncAYRS(base64.StdEncoding.EncodeToString)}, - "decode": &objects.UserFunction{Value: FuncASRYE(base64.StdEncoding.DecodeString)}, - - "raw_encode": &objects.UserFunction{Value: FuncAYRS(base64.RawStdEncoding.EncodeToString)}, - "raw_decode": &objects.UserFunction{Value: FuncASRYE(base64.RawStdEncoding.DecodeString)}, - - "url_encode": &objects.UserFunction{Value: FuncAYRS(base64.URLEncoding.EncodeToString)}, - "url_decode": &objects.UserFunction{Value: FuncASRYE(base64.URLEncoding.DecodeString)}, - - "raw_url_encode": &objects.UserFunction{Value: FuncAYRS(base64.RawURLEncoding.EncodeToString)}, - "raw_url_decode": &objects.UserFunction{Value: FuncASRYE(base64.RawURLEncoding.DecodeString)}, +var base64Module = map[string]tengo.Object{ + "encode": &tengo.UserFunction{ + Value: FuncAYRS(base64.StdEncoding.EncodeToString), + }, + "decode": &tengo.UserFunction{ + Value: FuncASRYE(base64.StdEncoding.DecodeString), + }, + "raw_encode": &tengo.UserFunction{ + Value: FuncAYRS(base64.RawStdEncoding.EncodeToString), + }, + "raw_decode": &tengo.UserFunction{ + Value: FuncASRYE(base64.RawStdEncoding.DecodeString), + }, + "url_encode": &tengo.UserFunction{ + Value: FuncAYRS(base64.URLEncoding.EncodeToString), + }, + "url_decode": &tengo.UserFunction{ + Value: FuncASRYE(base64.URLEncoding.DecodeString), + }, + "raw_url_encode": &tengo.UserFunction{ + Value: FuncAYRS(base64.RawURLEncoding.EncodeToString), + }, + "raw_url_decode": &tengo.UserFunction{ + Value: FuncASRYE(base64.RawURLEncoding.DecodeString), + }, } diff --git a/stdlib/base64_test.go b/stdlib/base64_test.go index b1157d6..5cc1540 100644 --- a/stdlib/base64_test.go +++ b/stdlib/base64_test.go @@ -2,11 +2,16 @@ package stdlib_test import "testing" -var base64Bytes1 = []byte{0x06, 0xAC, 0x76, 0x1B, 0x1D, 0x6A, 0xFA, 0x9D, 0xB1, 0xA0} -const base64Std = "Bqx2Gx1q+p2xoA==" -const base64URL = "Bqx2Gx1q-p2xoA==" -const base64RawStd = "Bqx2Gx1q+p2xoA" -const base64RawURL = "Bqx2Gx1q-p2xoA" +var base64Bytes1 = []byte{ + 0x06, 0xAC, 0x76, 0x1B, 0x1D, 0x6A, 0xFA, 0x9D, 0xB1, 0xA0, +} + +const ( + base64Std = "Bqx2Gx1q+p2xoA==" + base64URL = "Bqx2Gx1q-p2xoA==" + base64RawStd = "Bqx2Gx1q+p2xoA" + base64RawURL = "Bqx2Gx1q-p2xoA" +) func TestBase64(t *testing.T) { module(t, `base64`).call("encode", base64Bytes1).expect(base64Std) @@ -15,6 +20,8 @@ func TestBase64(t *testing.T) { module(t, `base64`).call("url_decode", base64URL).expect(base64Bytes1) module(t, `base64`).call("raw_encode", base64Bytes1).expect(base64RawStd) module(t, `base64`).call("raw_decode", base64RawStd).expect(base64Bytes1) - module(t, `base64`).call("raw_url_encode", base64Bytes1).expect(base64RawURL) - module(t, `base64`).call("raw_url_decode", base64RawURL).expect(base64Bytes1) + module(t, `base64`).call("raw_url_encode", base64Bytes1). + expect(base64RawURL) + module(t, `base64`).call("raw_url_decode", base64RawURL). + expect(base64Bytes1) } diff --git a/stdlib/builtin_modules.go b/stdlib/builtin_modules.go index 722461b..b8ddaca 100644 --- a/stdlib/builtin_modules.go +++ b/stdlib/builtin_modules.go @@ -1,16 +1,18 @@ package stdlib -import "github.com/d5/tengo/objects" +import ( + "github.com/d5/tengo" +) // BuiltinModules are builtin type standard library modules. -var BuiltinModules = map[string]map[string]objects.Object{ - "math": mathModule, - "os": osModule, - "text": textModule, - "times": timesModule, - "rand": randModule, - "fmt": fmtModule, - "json": jsonModule, +var BuiltinModules = map[string]map[string]tengo.Object{ + "math": mathModule, + "os": osModule, + "text": textModule, + "times": timesModule, + "rand": randModule, + "fmt": fmtModule, + "json": jsonModule, "base64": base64Module, - "hex": hexModule, + "hex": hexModule, } diff --git a/stdlib/errors.go b/stdlib/errors.go index a2942bb..eeefce5 100644 --- a/stdlib/errors.go +++ b/stdlib/errors.go @@ -1,11 +1,12 @@ package stdlib -import "github.com/d5/tengo/objects" +import ( + "github.com/d5/tengo" +) -func wrapError(err error) objects.Object { +func wrapError(err error) tengo.Object { if err == nil { - return objects.TrueValue + return tengo.TrueValue } - - return &objects.Error{Value: &objects.String{Value: err.Error()}} + return &tengo.Error{Value: &tengo.String{Value: err.Error()}} } diff --git a/stdlib/fmt.go b/stdlib/fmt.go index b8f6427..e8eeb95 100644 --- a/stdlib/fmt.go +++ b/stdlib/fmt.go @@ -4,36 +4,33 @@ import ( "fmt" "github.com/d5/tengo" - "github.com/d5/tengo/objects" ) -var fmtModule = map[string]objects.Object{ - "print": &objects.UserFunction{Name: "print", Value: fmtPrint}, - "printf": &objects.UserFunction{Name: "printf", Value: fmtPrintf}, - "println": &objects.UserFunction{Name: "println", Value: fmtPrintln}, - "sprintf": &objects.UserFunction{Name: "sprintf", Value: fmtSprintf}, +var fmtModule = map[string]tengo.Object{ + "print": &tengo.UserFunction{Name: "print", Value: fmtPrint}, + "printf": &tengo.UserFunction{Name: "printf", Value: fmtPrintf}, + "println": &tengo.UserFunction{Name: "println", Value: fmtPrintln}, + "sprintf": &tengo.UserFunction{Name: "sprintf", Value: fmtSprintf}, } -func fmtPrint(args ...objects.Object) (ret objects.Object, err error) { +func fmtPrint(args ...tengo.Object) (ret tengo.Object, err error) { printArgs, err := getPrintArgs(args...) if err != nil { return nil, err } - _, _ = fmt.Print(printArgs...) - return nil, nil } -func fmtPrintf(args ...objects.Object) (ret objects.Object, err error) { +func fmtPrintf(args ...tengo.Object) (ret tengo.Object, err error) { numArgs := len(args) if numArgs == 0 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - format, ok := args[0].(*objects.String) + format, ok := args[0].(*tengo.String) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "format", Expected: "string", Found: args[0].TypeName(), @@ -44,67 +41,61 @@ func fmtPrintf(args ...objects.Object) (ret objects.Object, err error) { return nil, nil } - s, err := objects.Format(format.Value, args[1:]...) + s, err := tengo.Format(format.Value, args[1:]...) if err != nil { return nil, err } - fmt.Print(s) - return nil, nil } -func fmtPrintln(args ...objects.Object) (ret objects.Object, err error) { +func fmtPrintln(args ...tengo.Object) (ret tengo.Object, err error) { printArgs, err := getPrintArgs(args...) if err != nil { return nil, err } - printArgs = append(printArgs, "\n") _, _ = fmt.Print(printArgs...) - return nil, nil } -func fmtSprintf(args ...objects.Object) (ret objects.Object, err error) { +func fmtSprintf(args ...tengo.Object) (ret tengo.Object, err error) { numArgs := len(args) if numArgs == 0 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - format, ok := args[0].(*objects.String) + format, ok := args[0].(*tengo.String) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "format", Expected: "string", Found: args[0].TypeName(), } } if numArgs == 1 { - return format, nil // okay to return 'format' directly as String is immutable + // okay to return 'format' directly as String is immutable + return format, nil } - - s, err := objects.Format(format.Value, args[1:]...) + s, err := tengo.Format(format.Value, args[1:]...) if err != nil { return nil, err } - - return &objects.String{Value: s}, nil + return &tengo.String{Value: s}, nil } -func getPrintArgs(args ...objects.Object) ([]interface{}, error) { +func getPrintArgs(args ...tengo.Object) ([]interface{}, error) { var printArgs []interface{} l := 0 for _, arg := range args { - s, _ := objects.ToString(arg) + s, _ := tengo.ToString(arg) slen := len(s) - if l+slen > tengo.MaxStringLen { // make sure length does not exceed the limit - return nil, objects.ErrStringLimit + // make sure length does not exceed the limit + if l+slen > tengo.MaxStringLen { + return nil, tengo.ErrStringLimit } l += slen - printArgs = append(printArgs, s) } - return printArgs, nil } diff --git a/stdlib/fmt_test.go b/stdlib/fmt_test.go index 95a9f41..fcf9e5d 100644 --- a/stdlib/fmt_test.go +++ b/stdlib/fmt_test.go @@ -5,9 +5,15 @@ import "testing" func TestFmtSprintf(t *testing.T) { module(t, `fmt`).call("sprintf", "").expect("") module(t, `fmt`).call("sprintf", "foo").expect("foo") - module(t, `fmt`).call("sprintf", `foo %d %v %s`, 1, 2, "bar").expect("foo 1 2 bar") - module(t, `fmt`).call("sprintf", "foo %v", ARR{1, "bar", true}).expect(`foo [1, "bar", true]`) - module(t, `fmt`).call("sprintf", "foo %v %d", ARR{1, "bar", true}, 19).expect(`foo [1, "bar", true] 19`) - module(t, `fmt`).call("sprintf", "foo %v", MAP{"a": IMAP{"b": IMAP{"c": ARR{1, 2, 3}}}}).expect(`foo {a: {b: {c: [1, 2, 3]}}}`) - module(t, `fmt`).call("sprintf", "%v", IARR{1, IARR{2, IARR{3, 4}}}).expect(`[1, [2, [3, 4]]]`) + module(t, `fmt`).call("sprintf", `foo %d %v %s`, 1, 2, "bar"). + expect("foo 1 2 bar") + module(t, `fmt`).call("sprintf", "foo %v", ARR{1, "bar", true}). + expect(`foo [1, "bar", true]`) + module(t, `fmt`).call("sprintf", "foo %v %d", ARR{1, "bar", true}, 19). + expect(`foo [1, "bar", true] 19`) + module(t, `fmt`). + call("sprintf", "foo %v", MAP{"a": IMAP{"b": IMAP{"c": ARR{1, 2, 3}}}}). + expect(`foo {a: {b: {c: [1, 2, 3]}}}`) + module(t, `fmt`).call("sprintf", "%v", IARR{1, IARR{2, IARR{3, 4}}}). + expect(`[1, [2, [3, 4]]]`) } diff --git a/stdlib/func_typedefs.go b/stdlib/func_typedefs.go index c7bd11f..5661545 100644 --- a/stdlib/func_typedefs.go +++ b/stdlib/func_typedefs.go @@ -4,832 +4,743 @@ import ( "fmt" "github.com/d5/tengo" - "github.com/d5/tengo/objects" ) -// FuncAR transform a function of 'func()' signature -// into CallableFunc type. -func FuncAR(fn func()) objects.CallableFunc { - return func(args ...objects.Object) (ret objects.Object, err error) { +// FuncAR transform a function of 'func()' signature into CallableFunc type. +func FuncAR(fn func()) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 0 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - fn() - - return objects.UndefinedValue, nil + return tengo.UndefinedValue, nil } } -// FuncARI transform a function of 'func() int' signature -// into CallableFunc type. -func FuncARI(fn func() int) objects.CallableFunc { - return func(args ...objects.Object) (ret objects.Object, err error) { +// FuncARI transform a function of 'func() int' signature into CallableFunc +// type. +func FuncARI(fn func() int) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 0 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - return &objects.Int{Value: int64(fn())}, nil + return &tengo.Int{Value: int64(fn())}, nil } } -// FuncARI64 transform a function of 'func() int64' signature -// into CallableFunc type. -func FuncARI64(fn func() int64) objects.CallableFunc { - return func(args ...objects.Object) (ret objects.Object, err error) { +// FuncARI64 transform a function of 'func() int64' signature into CallableFunc +// type. +func FuncARI64(fn func() int64) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 0 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - return &objects.Int{Value: fn()}, nil + return &tengo.Int{Value: fn()}, nil } } -// FuncAI64RI64 transform a function of 'func(int64) int64' signature -// into CallableFunc type. -func FuncAI64RI64(fn func(int64) int64) objects.CallableFunc { - return func(args ...objects.Object) (ret objects.Object, err error) { +// FuncAI64RI64 transform a function of 'func(int64) int64' signature into +// CallableFunc type. +func FuncAI64RI64(fn func(int64) int64) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 1 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - i1, ok := objects.ToInt64(args[0]) + i1, ok := tengo.ToInt64(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "int(compatible)", Found: args[0].TypeName(), } } - - return &objects.Int{Value: fn(i1)}, nil + return &tengo.Int{Value: fn(i1)}, nil } } -// FuncAI64R transform a function of 'func(int64)' signature -// into CallableFunc type. -func FuncAI64R(fn func(int64)) objects.CallableFunc { - return func(args ...objects.Object) (ret objects.Object, err error) { +// FuncAI64R transform a function of 'func(int64)' signature into CallableFunc +// type. +func FuncAI64R(fn func(int64)) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 1 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - i1, ok := objects.ToInt64(args[0]) + i1, ok := tengo.ToInt64(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "int(compatible)", Found: args[0].TypeName(), } } - fn(i1) - - return objects.UndefinedValue, nil + return tengo.UndefinedValue, nil } } -// FuncARB transform a function of 'func() bool' signature -// into CallableFunc type. -func FuncARB(fn func() bool) objects.CallableFunc { - return func(args ...objects.Object) (ret objects.Object, err error) { +// FuncARB transform a function of 'func() bool' signature into CallableFunc +// type. +func FuncARB(fn func() bool) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 0 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - if fn() { - return objects.TrueValue, nil + return tengo.TrueValue, nil } - - return objects.FalseValue, nil + return tengo.FalseValue, nil } } -// FuncARE transform a function of 'func() error' signature -// into CallableFunc type. -func FuncARE(fn func() error) objects.CallableFunc { - return func(args ...objects.Object) (ret objects.Object, err error) { +// FuncARE transform a function of 'func() error' signature into CallableFunc +// type. +func FuncARE(fn func() error) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 0 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - return wrapError(fn()), nil } } -// FuncARS transform a function of 'func() string' signature -// into CallableFunc type. -func FuncARS(fn func() string) objects.CallableFunc { - return func(args ...objects.Object) (ret objects.Object, err error) { +// FuncARS transform a function of 'func() string' signature into CallableFunc +// type. +func FuncARS(fn func() string) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 0 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - s := fn() - if len(s) > tengo.MaxStringLen { - return nil, objects.ErrStringLimit + return nil, tengo.ErrStringLimit } - - return &objects.String{Value: s}, nil + return &tengo.String{Value: s}, nil } } -// FuncARSE transform a function of 'func() (string, error)' signature -// into CallableFunc type. -func FuncARSE(fn func() (string, error)) objects.CallableFunc { - return func(args ...objects.Object) (ret objects.Object, err error) { +// FuncARSE transform a function of 'func() (string, error)' signature into +// CallableFunc type. +func FuncARSE(fn func() (string, error)) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 0 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - res, err := fn() if err != nil { return wrapError(err), nil } - if len(res) > tengo.MaxStringLen { - return nil, objects.ErrStringLimit + return nil, tengo.ErrStringLimit } - - return &objects.String{Value: res}, nil + return &tengo.String{Value: res}, nil } } -// FuncARYE transform a function of 'func() ([]byte, error)' signature -// into CallableFunc type. -func FuncARYE(fn func() ([]byte, error)) objects.CallableFunc { - return func(args ...objects.Object) (ret objects.Object, err error) { +// FuncARYE transform a function of 'func() ([]byte, error)' signature into +// CallableFunc type. +func FuncARYE(fn func() ([]byte, error)) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 0 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - res, err := fn() if err != nil { return wrapError(err), nil } - if len(res) > tengo.MaxBytesLen { - return nil, objects.ErrBytesLimit + return nil, tengo.ErrBytesLimit } - - return &objects.Bytes{Value: res}, nil + return &tengo.Bytes{Value: res}, nil } } -// FuncARF transform a function of 'func() float64' signature -// into CallableFunc type. -func FuncARF(fn func() float64) objects.CallableFunc { - return func(args ...objects.Object) (ret objects.Object, err error) { +// FuncARF transform a function of 'func() float64' signature into CallableFunc +// type. +func FuncARF(fn func() float64) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 0 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - return &objects.Float{Value: fn()}, nil + return &tengo.Float{Value: fn()}, nil } } -// FuncARSs transform a function of 'func() []string' signature -// into CallableFunc type. -func FuncARSs(fn func() []string) objects.CallableFunc { - return func(args ...objects.Object) (ret objects.Object, err error) { +// FuncARSs transform a function of 'func() []string' signature into +// CallableFunc type. +func FuncARSs(fn func() []string) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 0 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - arr := &objects.Array{} + arr := &tengo.Array{} for _, elem := range fn() { if len(elem) > tengo.MaxStringLen { - return nil, objects.ErrStringLimit + return nil, tengo.ErrStringLimit } - - arr.Value = append(arr.Value, &objects.String{Value: elem}) + arr.Value = append(arr.Value, &tengo.String{Value: elem}) } - return arr, nil } } -// FuncARIsE transform a function of 'func() ([]int, error)' signature -// into CallableFunc type. -func FuncARIsE(fn func() ([]int, error)) objects.CallableFunc { - return func(args ...objects.Object) (ret objects.Object, err error) { +// FuncARIsE transform a function of 'func() ([]int, error)' signature into +// CallableFunc type. +func FuncARIsE(fn func() ([]int, error)) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 0 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - res, err := fn() if err != nil { return wrapError(err), nil } - - arr := &objects.Array{} + arr := &tengo.Array{} for _, v := range res { - arr.Value = append(arr.Value, &objects.Int{Value: int64(v)}) + arr.Value = append(arr.Value, &tengo.Int{Value: int64(v)}) } - return arr, nil } } -// FuncAIRIs transform a function of 'func(int) []int' signature -// into CallableFunc type. -func FuncAIRIs(fn func(int) []int) objects.CallableFunc { - return func(args ...objects.Object) (ret objects.Object, err error) { +// FuncAIRIs transform a function of 'func(int) []int' signature into +// CallableFunc type. +func FuncAIRIs(fn func(int) []int) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 1 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - i1, ok := objects.ToInt(args[0]) + i1, ok := tengo.ToInt(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "int(compatible)", Found: args[0].TypeName(), } } - res := fn(i1) - - arr := &objects.Array{} + arr := &tengo.Array{} for _, v := range res { - arr.Value = append(arr.Value, &objects.Int{Value: int64(v)}) + arr.Value = append(arr.Value, &tengo.Int{Value: int64(v)}) } - return arr, nil } } -// FuncAFRF transform a function of 'func(float64) float64' signature -// into CallableFunc type. -func FuncAFRF(fn func(float64) float64) objects.CallableFunc { - return func(args ...objects.Object) (ret objects.Object, err error) { +// FuncAFRF transform a function of 'func(float64) float64' signature into +// CallableFunc type. +func FuncAFRF(fn func(float64) float64) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 1 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - f1, ok := objects.ToFloat64(args[0]) + f1, ok := tengo.ToFloat64(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "float(compatible)", Found: args[0].TypeName(), } } - - return &objects.Float{Value: fn(f1)}, nil + return &tengo.Float{Value: fn(f1)}, nil } } -// FuncAIR transform a function of 'func(int)' signature -// into CallableFunc type. -func FuncAIR(fn func(int)) objects.CallableFunc { - return func(args ...objects.Object) (ret objects.Object, err error) { +// FuncAIR transform a function of 'func(int)' signature into CallableFunc type. +func FuncAIR(fn func(int)) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 1 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - i1, ok := objects.ToInt(args[0]) + i1, ok := tengo.ToInt(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "int(compatible)", Found: args[0].TypeName(), } } - fn(i1) - - return objects.UndefinedValue, nil + return tengo.UndefinedValue, nil } } -// FuncAIRF transform a function of 'func(int) float64' signature -// into CallableFunc type. -func FuncAIRF(fn func(int) float64) objects.CallableFunc { - return func(args ...objects.Object) (ret objects.Object, err error) { +// FuncAIRF transform a function of 'func(int) float64' signature into +// CallableFunc type. +func FuncAIRF(fn func(int) float64) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 1 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - i1, ok := objects.ToInt(args[0]) + i1, ok := tengo.ToInt(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "int(compatible)", Found: args[0].TypeName(), } } - - return &objects.Float{Value: fn(i1)}, nil + return &tengo.Float{Value: fn(i1)}, nil } } -// FuncAFRI transform a function of 'func(float64) int' signature -// into CallableFunc type. -func FuncAFRI(fn func(float64) int) objects.CallableFunc { - return func(args ...objects.Object) (ret objects.Object, err error) { +// FuncAFRI transform a function of 'func(float64) int' signature into +// CallableFunc type. +func FuncAFRI(fn func(float64) int) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 1 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - f1, ok := objects.ToFloat64(args[0]) + f1, ok := tengo.ToFloat64(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "float(compatible)", Found: args[0].TypeName(), } } - - return &objects.Int{Value: int64(fn(f1))}, nil + return &tengo.Int{Value: int64(fn(f1))}, nil } } // FuncAFFRF transform a function of 'func(float64, float64) float64' signature // into CallableFunc type. -func FuncAFFRF(fn func(float64, float64) float64) objects.CallableFunc { - return func(args ...objects.Object) (ret objects.Object, err error) { +func FuncAFFRF(fn func(float64, float64) float64) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 2 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - f1, ok := objects.ToFloat64(args[0]) + f1, ok := tengo.ToFloat64(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "float(compatible)", Found: args[0].TypeName(), } } - - f2, ok := objects.ToFloat64(args[1]) + f2, ok := tengo.ToFloat64(args[1]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "second", Expected: "float(compatible)", Found: args[1].TypeName(), } } - - return &objects.Float{Value: fn(f1, f2)}, nil + return &tengo.Float{Value: fn(f1, f2)}, nil } } // FuncAIFRF transform a function of 'func(int, float64) float64' signature // into CallableFunc type. -func FuncAIFRF(fn func(int, float64) float64) objects.CallableFunc { - return func(args ...objects.Object) (ret objects.Object, err error) { +func FuncAIFRF(fn func(int, float64) float64) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 2 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - i1, ok := objects.ToInt(args[0]) + i1, ok := tengo.ToInt(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "int(compatible)", Found: args[0].TypeName(), } } - - f2, ok := objects.ToFloat64(args[1]) + f2, ok := tengo.ToFloat64(args[1]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "second", Expected: "float(compatible)", Found: args[1].TypeName(), } } - - return &objects.Float{Value: fn(i1, f2)}, nil + return &tengo.Float{Value: fn(i1, f2)}, nil } } // FuncAFIRF transform a function of 'func(float64, int) float64' signature // into CallableFunc type. -func FuncAFIRF(fn func(float64, int) float64) objects.CallableFunc { - return func(args ...objects.Object) (ret objects.Object, err error) { +func FuncAFIRF(fn func(float64, int) float64) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 2 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - f1, ok := objects.ToFloat64(args[0]) + f1, ok := tengo.ToFloat64(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "float(compatible)", Found: args[0].TypeName(), } } - - i2, ok := objects.ToInt(args[1]) + i2, ok := tengo.ToInt(args[1]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "second", Expected: "int(compatible)", Found: args[1].TypeName(), } } - - return &objects.Float{Value: fn(f1, i2)}, nil + return &tengo.Float{Value: fn(f1, i2)}, nil } } // FuncAFIRB transform a function of 'func(float64, int) bool' signature // into CallableFunc type. -func FuncAFIRB(fn func(float64, int) bool) objects.CallableFunc { - return func(args ...objects.Object) (ret objects.Object, err error) { +func FuncAFIRB(fn func(float64, int) bool) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 2 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - f1, ok := objects.ToFloat64(args[0]) + f1, ok := tengo.ToFloat64(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "float(compatible)", Found: args[0].TypeName(), } } - - i2, ok := objects.ToInt(args[1]) + i2, ok := tengo.ToInt(args[1]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "second", Expected: "int(compatible)", Found: args[1].TypeName(), } } - if fn(f1, i2) { - return objects.TrueValue, nil + return tengo.TrueValue, nil } - - return objects.FalseValue, nil + return tengo.FalseValue, nil } } // FuncAFRB transform a function of 'func(float64) bool' signature // into CallableFunc type. -func FuncAFRB(fn func(float64) bool) objects.CallableFunc { - return func(args ...objects.Object) (ret objects.Object, err error) { +func FuncAFRB(fn func(float64) bool) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 1 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - f1, ok := objects.ToFloat64(args[0]) + f1, ok := tengo.ToFloat64(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "float(compatible)", Found: args[0].TypeName(), } } - if fn(f1) { - return objects.TrueValue, nil + return tengo.TrueValue, nil } - - return objects.FalseValue, nil + return tengo.FalseValue, nil } } -// FuncASRS transform a function of 'func(string) string' signature into CallableFunc type. -// User function will return 'true' if underlying native function returns nil. -func FuncASRS(fn func(string) string) objects.CallableFunc { - return func(args ...objects.Object) (objects.Object, error) { +// FuncASRS transform a function of 'func(string) string' signature into +// CallableFunc type. User function will return 'true' if underlying native +// function returns nil. +func FuncASRS(fn func(string) string) tengo.CallableFunc { + return func(args ...tengo.Object) (tengo.Object, error) { if len(args) != 1 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - s1, ok := objects.ToString(args[0]) + s1, ok := tengo.ToString(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), } } - s := fn(s1) - if len(s) > tengo.MaxStringLen { - return nil, objects.ErrStringLimit + return nil, tengo.ErrStringLimit } - - return &objects.String{Value: s}, nil + return &tengo.String{Value: s}, nil } } -// FuncASRSs transform a function of 'func(string) []string' signature into CallableFunc type. -func FuncASRSs(fn func(string) []string) objects.CallableFunc { - return func(args ...objects.Object) (objects.Object, error) { +// FuncASRSs transform a function of 'func(string) []string' signature into +// CallableFunc type. +func FuncASRSs(fn func(string) []string) tengo.CallableFunc { + return func(args ...tengo.Object) (tengo.Object, error) { if len(args) != 1 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - s1, ok := objects.ToString(args[0]) + s1, ok := tengo.ToString(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), } } - res := fn(s1) - - arr := &objects.Array{} + arr := &tengo.Array{} for _, elem := range res { if len(elem) > tengo.MaxStringLen { - return nil, objects.ErrStringLimit + return nil, tengo.ErrStringLimit } - - arr.Value = append(arr.Value, &objects.String{Value: elem}) + arr.Value = append(arr.Value, &tengo.String{Value: elem}) } - return arr, nil } } -// FuncASRSE transform a function of 'func(string) (string, error)' signature into CallableFunc type. -// User function will return 'true' if underlying native function returns nil. -func FuncASRSE(fn func(string) (string, error)) objects.CallableFunc { - return func(args ...objects.Object) (objects.Object, error) { +// FuncASRSE transform a function of 'func(string) (string, error)' signature +// into CallableFunc type. User function will return 'true' if underlying +// native function returns nil. +func FuncASRSE(fn func(string) (string, error)) tengo.CallableFunc { + return func(args ...tengo.Object) (tengo.Object, error) { if len(args) != 1 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - s1, ok := objects.ToString(args[0]) + s1, ok := tengo.ToString(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), } } - res, err := fn(s1) if err != nil { return wrapError(err), nil } - if len(res) > tengo.MaxStringLen { - return nil, objects.ErrStringLimit + return nil, tengo.ErrStringLimit } - - return &objects.String{Value: res}, nil + return &tengo.String{Value: res}, nil } } -// FuncASRE transform a function of 'func(string) error' signature into CallableFunc type. -// User function will return 'true' if underlying native function returns nil. -func FuncASRE(fn func(string) error) objects.CallableFunc { - return func(args ...objects.Object) (objects.Object, error) { +// FuncASRE transform a function of 'func(string) error' signature into +// CallableFunc type. User function will return 'true' if underlying native +// function returns nil. +func FuncASRE(fn func(string) error) tengo.CallableFunc { + return func(args ...tengo.Object) (tengo.Object, error) { if len(args) != 1 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - s1, ok := objects.ToString(args[0]) + s1, ok := tengo.ToString(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), } } - return wrapError(fn(s1)), nil } } -// FuncASSRE transform a function of 'func(string, string) error' signature into CallableFunc type. -// User function will return 'true' if underlying native function returns nil. -func FuncASSRE(fn func(string, string) error) objects.CallableFunc { - return func(args ...objects.Object) (objects.Object, error) { +// FuncASSRE transform a function of 'func(string, string) error' signature +// into CallableFunc type. User function will return 'true' if underlying +// native function returns nil. +func FuncASSRE(fn func(string, string) error) tengo.CallableFunc { + return func(args ...tengo.Object) (tengo.Object, error) { if len(args) != 2 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - s1, ok := objects.ToString(args[0]) + s1, ok := tengo.ToString(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), } } - - s2, ok := objects.ToString(args[1]) + s2, ok := tengo.ToString(args[1]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "second", Expected: "string(compatible)", Found: args[1].TypeName(), } } - return wrapError(fn(s1, s2)), nil } } -// FuncASSRSs transform a function of 'func(string, string) []string' signature into CallableFunc type. -func FuncASSRSs(fn func(string, string) []string) objects.CallableFunc { - return func(args ...objects.Object) (objects.Object, error) { +// FuncASSRSs transform a function of 'func(string, string) []string' +// signature into CallableFunc type. +func FuncASSRSs(fn func(string, string) []string) tengo.CallableFunc { + return func(args ...tengo.Object) (tengo.Object, error) { if len(args) != 2 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - s1, ok := objects.ToString(args[0]) + s1, ok := tengo.ToString(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), } } - - s2, ok := objects.ToString(args[1]) + s2, ok := tengo.ToString(args[1]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[1].TypeName(), } } - - arr := &objects.Array{} + arr := &tengo.Array{} for _, res := range fn(s1, s2) { if len(res) > tengo.MaxStringLen { - return nil, objects.ErrStringLimit + return nil, tengo.ErrStringLimit } - - arr.Value = append(arr.Value, &objects.String{Value: res}) + arr.Value = append(arr.Value, &tengo.String{Value: res}) } - return arr, nil } } -// FuncASSIRSs transform a function of 'func(string, string, int) []string' signature into CallableFunc type. -func FuncASSIRSs(fn func(string, string, int) []string) objects.CallableFunc { - return func(args ...objects.Object) (objects.Object, error) { +// FuncASSIRSs transform a function of 'func(string, string, int) []string' +// signature into CallableFunc type. +func FuncASSIRSs(fn func(string, string, int) []string) tengo.CallableFunc { + return func(args ...tengo.Object) (tengo.Object, error) { if len(args) != 3 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - s1, ok := objects.ToString(args[0]) + s1, ok := tengo.ToString(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), } } - - s2, ok := objects.ToString(args[1]) + s2, ok := tengo.ToString(args[1]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "second", Expected: "string(compatible)", Found: args[1].TypeName(), } } - - i3, ok := objects.ToInt(args[2]) + i3, ok := tengo.ToInt(args[2]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "third", Expected: "int(compatible)", Found: args[2].TypeName(), } } - - arr := &objects.Array{} + arr := &tengo.Array{} for _, res := range fn(s1, s2, i3) { if len(res) > tengo.MaxStringLen { - return nil, objects.ErrStringLimit + return nil, tengo.ErrStringLimit } - - arr.Value = append(arr.Value, &objects.String{Value: res}) + arr.Value = append(arr.Value, &tengo.String{Value: res}) } - return arr, nil } } -// FuncASSRI transform a function of 'func(string, string) int' signature into CallableFunc type. -func FuncASSRI(fn func(string, string) int) objects.CallableFunc { - return func(args ...objects.Object) (objects.Object, error) { +// FuncASSRI transform a function of 'func(string, string) int' signature into +// CallableFunc type. +func FuncASSRI(fn func(string, string) int) tengo.CallableFunc { + return func(args ...tengo.Object) (tengo.Object, error) { if len(args) != 2 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - s1, ok := objects.ToString(args[0]) + s1, ok := tengo.ToString(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), } } - - s2, ok := objects.ToString(args[1]) + s2, ok := tengo.ToString(args[1]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "second", Expected: "string(compatible)", Found: args[0].TypeName(), } } - - return &objects.Int{Value: int64(fn(s1, s2))}, nil + return &tengo.Int{Value: int64(fn(s1, s2))}, nil } } -// FuncASSRS transform a function of 'func(string, string) string' signature into CallableFunc type. -func FuncASSRS(fn func(string, string) string) objects.CallableFunc { - return func(args ...objects.Object) (objects.Object, error) { +// FuncASSRS transform a function of 'func(string, string) string' signature +// into CallableFunc type. +func FuncASSRS(fn func(string, string) string) tengo.CallableFunc { + return func(args ...tengo.Object) (tengo.Object, error) { if len(args) != 2 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - s1, ok := objects.ToString(args[0]) + s1, ok := tengo.ToString(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), } } - - s2, ok := objects.ToString(args[1]) + s2, ok := tengo.ToString(args[1]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "second", Expected: "string(compatible)", Found: args[1].TypeName(), } } - s := fn(s1, s2) - if len(s) > tengo.MaxStringLen { - return nil, objects.ErrStringLimit + return nil, tengo.ErrStringLimit } - - return &objects.String{Value: s}, nil + return &tengo.String{Value: s}, nil } } -// FuncASSRB transform a function of 'func(string, string) bool' signature into CallableFunc type. -func FuncASSRB(fn func(string, string) bool) objects.CallableFunc { - return func(args ...objects.Object) (objects.Object, error) { +// FuncASSRB transform a function of 'func(string, string) bool' signature +// into CallableFunc type. +func FuncASSRB(fn func(string, string) bool) tengo.CallableFunc { + return func(args ...tengo.Object) (tengo.Object, error) { if len(args) != 2 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - s1, ok := objects.ToString(args[0]) + s1, ok := tengo.ToString(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), } } - - s2, ok := objects.ToString(args[1]) + s2, ok := tengo.ToString(args[1]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "second", Expected: "string(compatible)", Found: args[1].TypeName(), } } - if fn(s1, s2) { - return objects.TrueValue, nil + return tengo.TrueValue, nil } - - return objects.FalseValue, nil + return tengo.FalseValue, nil } } -// FuncASsSRS transform a function of 'func([]string, string) string' signature into CallableFunc type. -func FuncASsSRS(fn func([]string, string) string) objects.CallableFunc { - return func(args ...objects.Object) (objects.Object, error) { +// FuncASsSRS transform a function of 'func([]string, string) string' signature +// into CallableFunc type. +func FuncASsSRS(fn func([]string, string) string) tengo.CallableFunc { + return func(args ...tengo.Object) (tengo.Object, error) { if len(args) != 2 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - var ss1 []string switch arg0 := args[0].(type) { - case *objects.Array: + case *tengo.Array: for idx, a := range arg0.Value { - as, ok := objects.ToString(a) + as, ok := tengo.ToString(a) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: fmt.Sprintf("first[%d]", idx), Expected: "string(compatible)", Found: a.TypeName(), @@ -837,11 +748,11 @@ func FuncASsSRS(fn func([]string, string) string) objects.CallableFunc { } ss1 = append(ss1, as) } - case *objects.ImmutableArray: + case *tengo.ImmutableArray: for idx, a := range arg0.Value { - as, ok := objects.ToString(a) + as, ok := tengo.ToString(a) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: fmt.Sprintf("first[%d]", idx), Expected: "string(compatible)", Found: a.TypeName(), @@ -850,329 +761,289 @@ func FuncASsSRS(fn func([]string, string) string) objects.CallableFunc { ss1 = append(ss1, as) } default: - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "array", Found: args[0].TypeName(), } } - - s2, ok := objects.ToString(args[1]) + s2, ok := tengo.ToString(args[1]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "second", Expected: "string(compatible)", Found: args[1].TypeName(), } } - s := fn(ss1, s2) if len(s) > tengo.MaxStringLen { - return nil, objects.ErrStringLimit + return nil, tengo.ErrStringLimit } - - return &objects.String{Value: s}, nil + return &tengo.String{Value: s}, nil } } // FuncASI64RE transform a function of 'func(string, int64) error' signature // into CallableFunc type. -func FuncASI64RE(fn func(string, int64) error) objects.CallableFunc { - return func(args ...objects.Object) (ret objects.Object, err error) { +func FuncASI64RE(fn func(string, int64) error) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 2 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - s1, ok := objects.ToString(args[0]) + s1, ok := tengo.ToString(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), } } - - i2, ok := objects.ToInt64(args[1]) + i2, ok := tengo.ToInt64(args[1]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "second", Expected: "int(compatible)", Found: args[1].TypeName(), } } - return wrapError(fn(s1, i2)), nil } } // FuncAIIRE transform a function of 'func(int, int) error' signature // into CallableFunc type. -func FuncAIIRE(fn func(int, int) error) objects.CallableFunc { - return func(args ...objects.Object) (ret objects.Object, err error) { +func FuncAIIRE(fn func(int, int) error) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 2 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - i1, ok := objects.ToInt(args[0]) + i1, ok := tengo.ToInt(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "int(compatible)", Found: args[0].TypeName(), } } - - i2, ok := objects.ToInt(args[1]) + i2, ok := tengo.ToInt(args[1]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "second", Expected: "int(compatible)", Found: args[1].TypeName(), } } - return wrapError(fn(i1, i2)), nil } } // FuncASIRS transform a function of 'func(string, int) string' signature // into CallableFunc type. -func FuncASIRS(fn func(string, int) string) objects.CallableFunc { - return func(args ...objects.Object) (ret objects.Object, err error) { +func FuncASIRS(fn func(string, int) string) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 2 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - s1, ok := objects.ToString(args[0]) + s1, ok := tengo.ToString(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), } } - - i2, ok := objects.ToInt(args[1]) + i2, ok := tengo.ToInt(args[1]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "second", Expected: "int(compatible)", Found: args[1].TypeName(), } } - s := fn(s1, i2) - if len(s) > tengo.MaxStringLen { - return nil, objects.ErrStringLimit + return nil, tengo.ErrStringLimit } - - return &objects.String{Value: s}, nil + return &tengo.String{Value: s}, nil } } // FuncASIIRE transform a function of 'func(string, int, int) error' signature // into CallableFunc type. -func FuncASIIRE(fn func(string, int, int) error) objects.CallableFunc { - return func(args ...objects.Object) (ret objects.Object, err error) { +func FuncASIIRE(fn func(string, int, int) error) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 3 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - s1, ok := objects.ToString(args[0]) + s1, ok := tengo.ToString(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), } } - - i2, ok := objects.ToInt(args[1]) + i2, ok := tengo.ToInt(args[1]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "second", Expected: "int(compatible)", Found: args[1].TypeName(), } } - - i3, ok := objects.ToInt(args[2]) + i3, ok := tengo.ToInt(args[2]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "third", Expected: "int(compatible)", Found: args[2].TypeName(), } } - return wrapError(fn(s1, i2, i3)), nil } } // FuncAYRIE transform a function of 'func([]byte) (int, error)' signature // into CallableFunc type. -func FuncAYRIE(fn func([]byte) (int, error)) objects.CallableFunc { - return func(args ...objects.Object) (ret objects.Object, err error) { +func FuncAYRIE(fn func([]byte) (int, error)) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 1 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - y1, ok := objects.ToByteSlice(args[0]) + y1, ok := tengo.ToByteSlice(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "bytes(compatible)", Found: args[0].TypeName(), } } - res, err := fn(y1) if err != nil { return wrapError(err), nil } - - return &objects.Int{Value: int64(res)}, nil + return &tengo.Int{Value: int64(res)}, nil } } -// FuncAYRS transform a function of 'func([]byte) string' signature -// into CallableFunc type. -func FuncAYRS(fn func([]byte) string) objects.CallableFunc { - return func(args ...objects.Object) (ret objects.Object, err error) { +// FuncAYRS transform a function of 'func([]byte) string' signature into +// CallableFunc type. +func FuncAYRS(fn func([]byte) string) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 1 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - y1, ok := objects.ToByteSlice(args[0]) + y1, ok := tengo.ToByteSlice(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "bytes(compatible)", Found: args[0].TypeName(), } } - res := fn(y1) - - return &objects.String{Value: res}, nil + return &tengo.String{Value: res}, nil } } // FuncASRIE transform a function of 'func(string) (int, error)' signature // into CallableFunc type. -func FuncASRIE(fn func(string) (int, error)) objects.CallableFunc { - return func(args ...objects.Object) (ret objects.Object, err error) { +func FuncASRIE(fn func(string) (int, error)) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 1 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - s1, ok := objects.ToString(args[0]) + s1, ok := tengo.ToString(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), } } - res, err := fn(s1) if err != nil { return wrapError(err), nil } - - return &objects.Int{Value: int64(res)}, nil + return &tengo.Int{Value: int64(res)}, nil } } // FuncASRYE transform a function of 'func(string) ([]byte, error)' signature // into CallableFunc type. -func FuncASRYE(fn func(string) ([]byte, error)) objects.CallableFunc { - return func(args ...objects.Object) (ret objects.Object, err error) { +func FuncASRYE(fn func(string) ([]byte, error)) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 1 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - s1, ok := objects.ToString(args[0]) + s1, ok := tengo.ToString(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), } } - res, err := fn(s1) if err != nil { return wrapError(err), nil } - if len(res) > tengo.MaxBytesLen { - return nil, objects.ErrBytesLimit + return nil, tengo.ErrBytesLimit } - - return &objects.Bytes{Value: res}, nil + return &tengo.Bytes{Value: res}, nil } } // FuncAIRSsE transform a function of 'func(int) ([]string, error)' signature // into CallableFunc type. -func FuncAIRSsE(fn func(int) ([]string, error)) objects.CallableFunc { - return func(args ...objects.Object) (ret objects.Object, err error) { +func FuncAIRSsE(fn func(int) ([]string, error)) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 1 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - i1, ok := objects.ToInt(args[0]) + i1, ok := tengo.ToInt(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "int(compatible)", Found: args[0].TypeName(), } } - res, err := fn(i1) if err != nil { return wrapError(err), nil } - - arr := &objects.Array{} + arr := &tengo.Array{} for _, r := range res { if len(r) > tengo.MaxStringLen { - return nil, objects.ErrStringLimit + return nil, tengo.ErrStringLimit } - - arr.Value = append(arr.Value, &objects.String{Value: r}) + arr.Value = append(arr.Value, &tengo.String{Value: r}) } - return arr, nil } } -// FuncAIRS transform a function of 'func(int) string' signature -// into CallableFunc type. -func FuncAIRS(fn func(int) string) objects.CallableFunc { - return func(args ...objects.Object) (ret objects.Object, err error) { +// FuncAIRS transform a function of 'func(int) string' signature into +// CallableFunc type. +func FuncAIRS(fn func(int) string) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 1 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - i1, ok := objects.ToInt(args[0]) + i1, ok := tengo.ToInt(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "int(compatible)", Found: args[0].TypeName(), } } - s := fn(i1) - if len(s) > tengo.MaxStringLen { - return nil, objects.ErrStringLimit + return nil, tengo.ErrStringLimit } - - return &objects.String{Value: s}, nil + return &tengo.String{Value: s}, nil } } diff --git a/stdlib/func_typedefs_test.go b/stdlib/func_typedefs_test.go index c78b03f..e0ba2da 100644 --- a/stdlib/func_typedefs_test.go +++ b/stdlib/func_typedefs_test.go @@ -6,364 +6,417 @@ import ( "strings" "testing" - "github.com/d5/tengo/assert" - "github.com/d5/tengo/objects" + "github.com/d5/tengo" + "github.com/d5/tengo/internal/require" "github.com/d5/tengo/stdlib" ) func TestFuncAIR(t *testing.T) { uf := stdlib.FuncAIR(func(int) {}) - ret, err := funcCall(uf, &objects.Int{Value: 10}) - assert.NoError(t, err) - assert.Equal(t, objects.UndefinedValue, ret) + ret, err := funcCall(uf, &tengo.Int{Value: 10}) + require.NoError(t, err) + require.Equal(t, tengo.UndefinedValue, ret) _, err = funcCall(uf) - assert.Equal(t, objects.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrWrongNumArguments, err) } func TestFuncAR(t *testing.T) { uf := stdlib.FuncAR(func() {}) ret, err := funcCall(uf) - assert.NoError(t, err) - assert.Equal(t, objects.UndefinedValue, ret) - _, err = funcCall(uf, objects.TrueValue) - assert.Equal(t, objects.ErrWrongNumArguments, err) + require.NoError(t, err) + require.Equal(t, tengo.UndefinedValue, ret) + _, err = funcCall(uf, tengo.TrueValue) + require.Equal(t, tengo.ErrWrongNumArguments, err) } func TestFuncARI(t *testing.T) { uf := stdlib.FuncARI(func() int { return 10 }) ret, err := funcCall(uf) - assert.NoError(t, err) - assert.Equal(t, &objects.Int{Value: 10}, ret) - _, err = funcCall(uf, objects.TrueValue) - assert.Equal(t, objects.ErrWrongNumArguments, err) + require.NoError(t, err) + require.Equal(t, &tengo.Int{Value: 10}, ret) + _, err = funcCall(uf, tengo.TrueValue) + require.Equal(t, tengo.ErrWrongNumArguments, err) } func TestFuncARE(t *testing.T) { uf := stdlib.FuncARE(func() error { return nil }) ret, err := funcCall(uf) - assert.NoError(t, err) - assert.Equal(t, objects.TrueValue, ret) + require.NoError(t, err) + require.Equal(t, tengo.TrueValue, ret) uf = stdlib.FuncARE(func() error { return errors.New("some error") }) ret, err = funcCall(uf) - assert.NoError(t, err) - assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) - _, err = funcCall(uf, objects.TrueValue) - assert.Equal(t, objects.ErrWrongNumArguments, err) + require.NoError(t, err) + require.Equal(t, &tengo.Error{ + Value: &tengo.String{Value: "some error"}, + }, ret) + _, err = funcCall(uf, tengo.TrueValue) + require.Equal(t, tengo.ErrWrongNumArguments, err) } func TestFuncARIsE(t *testing.T) { - uf := stdlib.FuncARIsE(func() ([]int, error) { return []int{1, 2, 3}, nil }) + uf := stdlib.FuncARIsE(func() ([]int, error) { + return []int{1, 2, 3}, nil + }) ret, err := funcCall(uf) - assert.NoError(t, err) - assert.Equal(t, array(&objects.Int{Value: 1}, &objects.Int{Value: 2}, &objects.Int{Value: 3}), ret) - uf = stdlib.FuncARIsE(func() ([]int, error) { return nil, errors.New("some error") }) + require.NoError(t, err) + require.Equal(t, array(&tengo.Int{Value: 1}, + &tengo.Int{Value: 2}, &tengo.Int{Value: 3}), ret) + uf = stdlib.FuncARIsE(func() ([]int, error) { + return nil, errors.New("some error") + }) ret, err = funcCall(uf) - assert.NoError(t, err) - assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) - _, err = funcCall(uf, objects.TrueValue) - assert.Equal(t, objects.ErrWrongNumArguments, err) + require.NoError(t, err) + require.Equal(t, &tengo.Error{ + Value: &tengo.String{Value: "some error"}, + }, ret) + _, err = funcCall(uf, tengo.TrueValue) + require.Equal(t, tengo.ErrWrongNumArguments, err) } func TestFuncARS(t *testing.T) { uf := stdlib.FuncARS(func() string { return "foo" }) ret, err := funcCall(uf) - assert.NoError(t, err) - assert.Equal(t, &objects.String{Value: "foo"}, ret) - _, err = funcCall(uf, objects.TrueValue) - assert.Equal(t, objects.ErrWrongNumArguments, err) + require.NoError(t, err) + require.Equal(t, &tengo.String{Value: "foo"}, ret) + _, err = funcCall(uf, tengo.TrueValue) + require.Equal(t, tengo.ErrWrongNumArguments, err) } func TestFuncARSE(t *testing.T) { uf := stdlib.FuncARSE(func() (string, error) { return "foo", nil }) ret, err := funcCall(uf) - assert.NoError(t, err) - assert.Equal(t, &objects.String{Value: "foo"}, ret) - uf = stdlib.FuncARSE(func() (string, error) { return "", errors.New("some error") }) + require.NoError(t, err) + require.Equal(t, &tengo.String{Value: "foo"}, ret) + uf = stdlib.FuncARSE(func() (string, error) { + return "", errors.New("some error") + }) ret, err = funcCall(uf) - assert.NoError(t, err) - assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) - _, err = funcCall(uf, objects.TrueValue) - assert.Equal(t, objects.ErrWrongNumArguments, err) + require.NoError(t, err) + require.Equal(t, &tengo.Error{ + Value: &tengo.String{Value: "some error"}, + }, ret) + _, err = funcCall(uf, tengo.TrueValue) + require.Equal(t, tengo.ErrWrongNumArguments, err) } func TestFuncARSs(t *testing.T) { uf := stdlib.FuncARSs(func() []string { return []string{"foo", "bar"} }) ret, err := funcCall(uf) - assert.NoError(t, err) - assert.Equal(t, array(&objects.String{Value: "foo"}, &objects.String{Value: "bar"}), ret) - _, err = funcCall(uf, objects.TrueValue) - assert.Equal(t, objects.ErrWrongNumArguments, err) + require.NoError(t, err) + require.Equal(t, array(&tengo.String{Value: "foo"}, + &tengo.String{Value: "bar"}), ret) + _, err = funcCall(uf, tengo.TrueValue) + require.Equal(t, tengo.ErrWrongNumArguments, err) } func TestFuncASRE(t *testing.T) { uf := stdlib.FuncASRE(func(a string) error { return nil }) - ret, err := funcCall(uf, &objects.String{Value: "foo"}) - assert.NoError(t, err) - assert.Equal(t, objects.TrueValue, ret) - uf = stdlib.FuncASRE(func(a string) error { return errors.New("some error") }) - ret, err = funcCall(uf, &objects.String{Value: "foo"}) - assert.NoError(t, err) - assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) + ret, err := funcCall(uf, &tengo.String{Value: "foo"}) + require.NoError(t, err) + require.Equal(t, tengo.TrueValue, ret) + uf = stdlib.FuncASRE(func(a string) error { + return errors.New("some error") + }) + ret, err = funcCall(uf, &tengo.String{Value: "foo"}) + require.NoError(t, err) + require.Equal(t, &tengo.Error{ + Value: &tengo.String{Value: "some error"}, + }, ret) _, err = funcCall(uf) - assert.Equal(t, objects.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrWrongNumArguments, err) } func TestFuncASRS(t *testing.T) { uf := stdlib.FuncASRS(func(a string) string { return a }) - ret, err := funcCall(uf, &objects.String{Value: "foo"}) - assert.NoError(t, err) - assert.Equal(t, &objects.String{Value: "foo"}, ret) + ret, err := funcCall(uf, &tengo.String{Value: "foo"}) + require.NoError(t, err) + require.Equal(t, &tengo.String{Value: "foo"}, ret) _, err = funcCall(uf) - assert.Equal(t, objects.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrWrongNumArguments, err) } func TestFuncASRSs(t *testing.T) { uf := stdlib.FuncASRSs(func(a string) []string { return []string{a} }) - ret, err := funcCall(uf, &objects.String{Value: "foo"}) - assert.NoError(t, err) - assert.Equal(t, array(&objects.String{Value: "foo"}), ret) + ret, err := funcCall(uf, &tengo.String{Value: "foo"}) + require.NoError(t, err) + require.Equal(t, array(&tengo.String{Value: "foo"}), ret) _, err = funcCall(uf) - assert.Equal(t, objects.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrWrongNumArguments, err) } func TestFuncASI64RE(t *testing.T) { uf := stdlib.FuncASI64RE(func(a string, b int64) error { return nil }) - ret, err := funcCall(uf, &objects.String{Value: "foo"}, &objects.Int{Value: 5}) - assert.NoError(t, err) - assert.Equal(t, objects.TrueValue, ret) - uf = stdlib.FuncASI64RE(func(a string, b int64) error { return errors.New("some error") }) - ret, err = funcCall(uf, &objects.String{Value: "foo"}, &objects.Int{Value: 5}) - assert.NoError(t, err) - assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) + ret, err := funcCall(uf, &tengo.String{Value: "foo"}, &tengo.Int{Value: 5}) + require.NoError(t, err) + require.Equal(t, tengo.TrueValue, ret) + uf = stdlib.FuncASI64RE(func(a string, b int64) error { + return errors.New("some error") + }) + ret, err = funcCall(uf, &tengo.String{Value: "foo"}, &tengo.Int{Value: 5}) + require.NoError(t, err) + require.Equal(t, + &tengo.Error{Value: &tengo.String{Value: "some error"}}, ret) _, err = funcCall(uf) - assert.Equal(t, objects.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrWrongNumArguments, err) } func TestFuncAIIRE(t *testing.T) { uf := stdlib.FuncAIIRE(func(a, b int) error { return nil }) - ret, err := funcCall(uf, &objects.Int{Value: 5}, &objects.Int{Value: 7}) - assert.NoError(t, err) - assert.Equal(t, objects.TrueValue, ret) - uf = stdlib.FuncAIIRE(func(a, b int) error { return errors.New("some error") }) - ret, err = funcCall(uf, &objects.Int{Value: 5}, &objects.Int{Value: 7}) - assert.NoError(t, err) - assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) + ret, err := funcCall(uf, &tengo.Int{Value: 5}, &tengo.Int{Value: 7}) + require.NoError(t, err) + require.Equal(t, tengo.TrueValue, ret) + uf = stdlib.FuncAIIRE(func(a, b int) error { + return errors.New("some error") + }) + ret, err = funcCall(uf, &tengo.Int{Value: 5}, &tengo.Int{Value: 7}) + require.NoError(t, err) + require.Equal(t, + &tengo.Error{Value: &tengo.String{Value: "some error"}}, ret) _, err = funcCall(uf) - assert.Equal(t, objects.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrWrongNumArguments, err) } func TestFuncASIIRE(t *testing.T) { uf := stdlib.FuncASIIRE(func(a string, b, c int) error { return nil }) - ret, err := funcCall(uf, &objects.String{Value: "foo"}, &objects.Int{Value: 5}, &objects.Int{Value: 7}) - assert.NoError(t, err) - assert.Equal(t, objects.TrueValue, ret) - uf = stdlib.FuncASIIRE(func(a string, b, c int) error { return errors.New("some error") }) - ret, err = funcCall(uf, &objects.String{Value: "foo"}, &objects.Int{Value: 5}, &objects.Int{Value: 7}) - assert.NoError(t, err) - assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) + ret, err := funcCall(uf, &tengo.String{Value: "foo"}, &tengo.Int{Value: 5}, + &tengo.Int{Value: 7}) + require.NoError(t, err) + require.Equal(t, tengo.TrueValue, ret) + uf = stdlib.FuncASIIRE(func(a string, b, c int) error { + return errors.New("some error") + }) + ret, err = funcCall(uf, &tengo.String{Value: "foo"}, &tengo.Int{Value: 5}, + &tengo.Int{Value: 7}) + require.NoError(t, err) + require.Equal(t, + &tengo.Error{Value: &tengo.String{Value: "some error"}}, ret) _, err = funcCall(uf) - assert.Equal(t, objects.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrWrongNumArguments, err) } func TestFuncASRSE(t *testing.T) { uf := stdlib.FuncASRSE(func(a string) (string, error) { return a, nil }) - ret, err := funcCall(uf, &objects.String{Value: "foo"}) - assert.NoError(t, err) - assert.Equal(t, &objects.String{Value: "foo"}, ret) - uf = stdlib.FuncASRSE(func(a string) (string, error) { return a, errors.New("some error") }) - ret, err = funcCall(uf, &objects.String{Value: "foo"}) - assert.NoError(t, err) - assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) + ret, err := funcCall(uf, &tengo.String{Value: "foo"}) + require.NoError(t, err) + require.Equal(t, &tengo.String{Value: "foo"}, ret) + uf = stdlib.FuncASRSE(func(a string) (string, error) { + return a, errors.New("some error") + }) + ret, err = funcCall(uf, &tengo.String{Value: "foo"}) + require.NoError(t, err) + require.Equal(t, + &tengo.Error{Value: &tengo.String{Value: "some error"}}, ret) _, err = funcCall(uf) - assert.Equal(t, objects.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrWrongNumArguments, err) } func TestFuncASSRE(t *testing.T) { uf := stdlib.FuncASSRE(func(a, b string) error { return nil }) - ret, err := funcCall(uf, &objects.String{Value: "foo"}, &objects.String{Value: "bar"}) - assert.NoError(t, err) - assert.Equal(t, objects.TrueValue, ret) - uf = stdlib.FuncASSRE(func(a, b string) error { return errors.New("some error") }) - ret, err = funcCall(uf, &objects.String{Value: "foo"}, &objects.String{Value: "bar"}) - assert.NoError(t, err) - assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) - _, err = funcCall(uf, &objects.String{Value: "foo"}) - assert.Equal(t, objects.ErrWrongNumArguments, err) + ret, err := funcCall(uf, &tengo.String{Value: "foo"}, + &tengo.String{Value: "bar"}) + require.NoError(t, err) + require.Equal(t, tengo.TrueValue, ret) + uf = stdlib.FuncASSRE(func(a, b string) error { + return errors.New("some error") + }) + ret, err = funcCall(uf, &tengo.String{Value: "foo"}, + &tengo.String{Value: "bar"}) + require.NoError(t, err) + require.Equal(t, + &tengo.Error{Value: &tengo.String{Value: "some error"}}, ret) + _, err = funcCall(uf, &tengo.String{Value: "foo"}) + require.Equal(t, tengo.ErrWrongNumArguments, err) } func TestFuncASsRS(t *testing.T) { - uf := stdlib.FuncASsSRS(func(a []string, b string) string { return strings.Join(a, b) }) - ret, err := funcCall(uf, array(&objects.String{Value: "foo"}, &objects.String{Value: "bar"}), &objects.String{Value: " "}) - assert.NoError(t, err) - assert.Equal(t, &objects.String{Value: "foo bar"}, ret) - _, err = funcCall(uf, &objects.String{Value: "foo"}) - assert.Equal(t, objects.ErrWrongNumArguments, err) + uf := stdlib.FuncASsSRS(func(a []string, b string) string { + return strings.Join(a, b) + }) + ret, err := funcCall(uf, array(&tengo.String{Value: "foo"}, + &tengo.String{Value: "bar"}), &tengo.String{Value: " "}) + require.NoError(t, err) + require.Equal(t, &tengo.String{Value: "foo bar"}, ret) + _, err = funcCall(uf, &tengo.String{Value: "foo"}) + require.Equal(t, tengo.ErrWrongNumArguments, err) } func TestFuncARF(t *testing.T) { uf := stdlib.FuncARF(func() float64 { return 10.0 }) ret, err := funcCall(uf) - assert.NoError(t, err) - assert.Equal(t, &objects.Float{Value: 10.0}, ret) - _, err = funcCall(uf, objects.TrueValue) - assert.Equal(t, objects.ErrWrongNumArguments, err) + require.NoError(t, err) + require.Equal(t, &tengo.Float{Value: 10.0}, ret) + _, err = funcCall(uf, tengo.TrueValue) + require.Equal(t, tengo.ErrWrongNumArguments, err) } func TestFuncAFRF(t *testing.T) { uf := stdlib.FuncAFRF(func(a float64) float64 { return a }) - ret, err := funcCall(uf, &objects.Float{Value: 10.0}) - assert.NoError(t, err) - assert.Equal(t, &objects.Float{Value: 10.0}, ret) + ret, err := funcCall(uf, &tengo.Float{Value: 10.0}) + require.NoError(t, err) + require.Equal(t, &tengo.Float{Value: 10.0}, ret) _, err = funcCall(uf) - assert.Equal(t, objects.ErrWrongNumArguments, err) - _, err = funcCall(uf, objects.TrueValue, objects.TrueValue) - assert.Equal(t, objects.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrWrongNumArguments, err) + _, err = funcCall(uf, tengo.TrueValue, tengo.TrueValue) + require.Equal(t, tengo.ErrWrongNumArguments, err) } func TestFuncAIRF(t *testing.T) { uf := stdlib.FuncAIRF(func(a int) float64 { return float64(a) }) - ret, err := funcCall(uf, &objects.Int{Value: 10.0}) - assert.NoError(t, err) - assert.Equal(t, &objects.Float{Value: 10.0}, ret) + ret, err := funcCall(uf, &tengo.Int{Value: 10.0}) + require.NoError(t, err) + require.Equal(t, &tengo.Float{Value: 10.0}, ret) _, err = funcCall(uf) - assert.Equal(t, objects.ErrWrongNumArguments, err) - _, err = funcCall(uf, objects.TrueValue, objects.TrueValue) - assert.Equal(t, objects.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrWrongNumArguments, err) + _, err = funcCall(uf, tengo.TrueValue, tengo.TrueValue) + require.Equal(t, tengo.ErrWrongNumArguments, err) } func TestFuncAFRI(t *testing.T) { uf := stdlib.FuncAFRI(func(a float64) int { return int(a) }) - ret, err := funcCall(uf, &objects.Float{Value: 10.5}) - assert.NoError(t, err) - assert.Equal(t, &objects.Int{Value: 10}, ret) + ret, err := funcCall(uf, &tengo.Float{Value: 10.5}) + require.NoError(t, err) + require.Equal(t, &tengo.Int{Value: 10}, ret) _, err = funcCall(uf) - assert.Equal(t, objects.ErrWrongNumArguments, err) - _, err = funcCall(uf, objects.TrueValue, objects.TrueValue) - assert.Equal(t, objects.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrWrongNumArguments, err) + _, err = funcCall(uf, tengo.TrueValue, tengo.TrueValue) + require.Equal(t, tengo.ErrWrongNumArguments, err) } func TestFuncAFRB(t *testing.T) { uf := stdlib.FuncAFRB(func(a float64) bool { return a > 0.0 }) - ret, err := funcCall(uf, &objects.Float{Value: 0.1}) - assert.NoError(t, err) - assert.Equal(t, objects.TrueValue, ret) + ret, err := funcCall(uf, &tengo.Float{Value: 0.1}) + require.NoError(t, err) + require.Equal(t, tengo.TrueValue, ret) _, err = funcCall(uf) - assert.Equal(t, objects.ErrWrongNumArguments, err) - _, err = funcCall(uf, objects.TrueValue, objects.TrueValue) - assert.Equal(t, objects.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrWrongNumArguments, err) + _, err = funcCall(uf, tengo.TrueValue, tengo.TrueValue) + require.Equal(t, tengo.ErrWrongNumArguments, err) } func TestFuncAFFRF(t *testing.T) { uf := stdlib.FuncAFFRF(func(a, b float64) float64 { return a + b }) - ret, err := funcCall(uf, &objects.Float{Value: 10.0}, &objects.Float{Value: 20.0}) - assert.NoError(t, err) - assert.Equal(t, &objects.Float{Value: 30.0}, ret) + ret, err := funcCall(uf, &tengo.Float{Value: 10.0}, + &tengo.Float{Value: 20.0}) + require.NoError(t, err) + require.Equal(t, &tengo.Float{Value: 30.0}, ret) _, err = funcCall(uf) - assert.Equal(t, objects.ErrWrongNumArguments, err) - _, err = funcCall(uf, objects.TrueValue) - assert.Equal(t, objects.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrWrongNumArguments, err) + _, err = funcCall(uf, tengo.TrueValue) + require.Equal(t, tengo.ErrWrongNumArguments, err) } func TestFuncASIRS(t *testing.T) { - uf := stdlib.FuncASIRS(func(a string, b int) string { return strings.Repeat(a, b) }) - ret, err := funcCall(uf, &objects.String{Value: "ab"}, &objects.Int{Value: 2}) - assert.NoError(t, err) - assert.Equal(t, &objects.String{Value: "abab"}, ret) + uf := stdlib.FuncASIRS(func(a string, b int) string { + return strings.Repeat(a, b) + }) + ret, err := funcCall(uf, &tengo.String{Value: "ab"}, &tengo.Int{Value: 2}) + require.NoError(t, err) + require.Equal(t, &tengo.String{Value: "abab"}, ret) _, err = funcCall(uf) - assert.Equal(t, objects.ErrWrongNumArguments, err) - _, err = funcCall(uf, objects.TrueValue) - assert.Equal(t, objects.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrWrongNumArguments, err) + _, err = funcCall(uf, tengo.TrueValue) + require.Equal(t, tengo.ErrWrongNumArguments, err) } func TestFuncAIFRF(t *testing.T) { uf := stdlib.FuncAIFRF(func(a int, b float64) float64 { return float64(a) + b }) - ret, err := funcCall(uf, &objects.Int{Value: 10}, &objects.Float{Value: 20.0}) - assert.NoError(t, err) - assert.Equal(t, &objects.Float{Value: 30.0}, ret) + ret, err := funcCall(uf, &tengo.Int{Value: 10}, &tengo.Float{Value: 20.0}) + require.NoError(t, err) + require.Equal(t, &tengo.Float{Value: 30.0}, ret) _, err = funcCall(uf) - assert.Equal(t, objects.ErrWrongNumArguments, err) - _, err = funcCall(uf, objects.TrueValue) - assert.Equal(t, objects.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrWrongNumArguments, err) + _, err = funcCall(uf, tengo.TrueValue) + require.Equal(t, tengo.ErrWrongNumArguments, err) } func TestFuncAFIRF(t *testing.T) { uf := stdlib.FuncAFIRF(func(a float64, b int) float64 { return a + float64(b) }) - ret, err := funcCall(uf, &objects.Float{Value: 10.0}, &objects.Int{Value: 20}) - assert.NoError(t, err) - assert.Equal(t, &objects.Float{Value: 30.0}, ret) + ret, err := funcCall(uf, &tengo.Float{Value: 10.0}, &tengo.Int{Value: 20}) + require.NoError(t, err) + require.Equal(t, &tengo.Float{Value: 30.0}, ret) _, err = funcCall(uf) - assert.Equal(t, objects.ErrWrongNumArguments, err) - _, err = funcCall(uf, objects.TrueValue) - assert.Equal(t, objects.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrWrongNumArguments, err) + _, err = funcCall(uf, tengo.TrueValue) + require.Equal(t, tengo.ErrWrongNumArguments, err) } func TestFuncAFIRB(t *testing.T) { uf := stdlib.FuncAFIRB(func(a float64, b int) bool { return a < float64(b) }) - ret, err := funcCall(uf, &objects.Float{Value: 10.0}, &objects.Int{Value: 20}) - assert.NoError(t, err) - assert.Equal(t, objects.TrueValue, ret) + ret, err := funcCall(uf, &tengo.Float{Value: 10.0}, &tengo.Int{Value: 20}) + require.NoError(t, err) + require.Equal(t, tengo.TrueValue, ret) _, err = funcCall(uf) - assert.Equal(t, objects.ErrWrongNumArguments, err) - _, err = funcCall(uf, objects.TrueValue) - assert.Equal(t, objects.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrWrongNumArguments, err) + _, err = funcCall(uf, tengo.TrueValue) + require.Equal(t, tengo.ErrWrongNumArguments, err) } func TestFuncAIRSsE(t *testing.T) { uf := stdlib.FuncAIRSsE(func(a int) ([]string, error) { return []string{"foo", "bar"}, nil }) - ret, err := funcCall(uf, &objects.Int{Value: 10}) - assert.NoError(t, err) - assert.Equal(t, array(&objects.String{Value: "foo"}, &objects.String{Value: "bar"}), ret) + ret, err := funcCall(uf, &tengo.Int{Value: 10}) + require.NoError(t, err) + require.Equal(t, array(&tengo.String{Value: "foo"}, + &tengo.String{Value: "bar"}), ret) uf = stdlib.FuncAIRSsE(func(a int) ([]string, error) { return nil, errors.New("some error") }) - ret, err = funcCall(uf, &objects.Int{Value: 10}) - assert.NoError(t, err) - assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) + ret, err = funcCall(uf, &tengo.Int{Value: 10}) + require.NoError(t, err) + require.Equal(t, + &tengo.Error{Value: &tengo.String{Value: "some error"}}, ret) _, err = funcCall(uf) - assert.Equal(t, objects.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrWrongNumArguments, err) } func TestFuncASSRSs(t *testing.T) { - uf := stdlib.FuncASSRSs(func(a, b string) []string { return []string{a, b} }) - ret, err := funcCall(uf, &objects.String{Value: "foo"}, &objects.String{Value: "bar"}) - assert.NoError(t, err) - assert.Equal(t, array(&objects.String{Value: "foo"}, &objects.String{Value: "bar"}), ret) + uf := stdlib.FuncASSRSs(func(a, b string) []string { + return []string{a, b} + }) + ret, err := funcCall(uf, &tengo.String{Value: "foo"}, + &tengo.String{Value: "bar"}) + require.NoError(t, err) + require.Equal(t, array(&tengo.String{Value: "foo"}, + &tengo.String{Value: "bar"}), ret) _, err = funcCall(uf) - assert.Equal(t, objects.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrWrongNumArguments, err) } func TestFuncASSIRSs(t *testing.T) { - uf := stdlib.FuncASSIRSs(func(a, b string, c int) []string { return []string{a, b, strconv.Itoa(c)} }) - ret, err := funcCall(uf, &objects.String{Value: "foo"}, &objects.String{Value: "bar"}, &objects.Int{Value: 5}) - assert.NoError(t, err) - assert.Equal(t, array(&objects.String{Value: "foo"}, &objects.String{Value: "bar"}, &objects.String{Value: "5"}), ret) + uf := stdlib.FuncASSIRSs(func(a, b string, c int) []string { + return []string{a, b, strconv.Itoa(c)} + }) + ret, err := funcCall(uf, &tengo.String{Value: "foo"}, + &tengo.String{Value: "bar"}, &tengo.Int{Value: 5}) + require.NoError(t, err) + require.Equal(t, array(&tengo.String{Value: "foo"}, + &tengo.String{Value: "bar"}, &tengo.String{Value: "5"}), ret) _, err = funcCall(uf) - assert.Equal(t, objects.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrWrongNumArguments, err) } func TestFuncARB(t *testing.T) { uf := stdlib.FuncARB(func() bool { return true }) ret, err := funcCall(uf) - assert.NoError(t, err) - assert.Equal(t, objects.TrueValue, ret) - _, err = funcCall(uf, objects.TrueValue) - assert.Equal(t, objects.ErrWrongNumArguments, err) + require.NoError(t, err) + require.Equal(t, tengo.TrueValue, ret) + _, err = funcCall(uf, tengo.TrueValue) + require.Equal(t, tengo.ErrWrongNumArguments, err) } func TestFuncARYE(t *testing.T) { @@ -371,130 +424,147 @@ func TestFuncARYE(t *testing.T) { return []byte("foo bar"), nil }) ret, err := funcCall(uf) - assert.NoError(t, err) - assert.Equal(t, &objects.Bytes{Value: []byte("foo bar")}, ret) + require.NoError(t, err) + require.Equal(t, &tengo.Bytes{Value: []byte("foo bar")}, ret) uf = stdlib.FuncARYE(func() ([]byte, error) { return nil, errors.New("some error") }) ret, err = funcCall(uf) - assert.NoError(t, err) - assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) - _, err = funcCall(uf, objects.TrueValue) - assert.Equal(t, objects.ErrWrongNumArguments, err) + require.NoError(t, err) + require.Equal(t, + &tengo.Error{Value: &tengo.String{Value: "some error"}}, ret) + _, err = funcCall(uf, tengo.TrueValue) + require.Equal(t, tengo.ErrWrongNumArguments, err) } func TestFuncASRIE(t *testing.T) { uf := stdlib.FuncASRIE(func(a string) (int, error) { return 5, nil }) - ret, err := funcCall(uf, &objects.String{Value: "foo"}) - assert.NoError(t, err) - assert.Equal(t, &objects.Int{Value: 5}, ret) - uf = stdlib.FuncASRIE(func(a string) (int, error) { return 0, errors.New("some error") }) - ret, err = funcCall(uf, &objects.String{Value: "foo"}) - assert.NoError(t, err) - assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) + ret, err := funcCall(uf, &tengo.String{Value: "foo"}) + require.NoError(t, err) + require.Equal(t, &tengo.Int{Value: 5}, ret) + uf = stdlib.FuncASRIE(func(a string) (int, error) { + return 0, errors.New("some error") + }) + ret, err = funcCall(uf, &tengo.String{Value: "foo"}) + require.NoError(t, err) + require.Equal(t, + &tengo.Error{Value: &tengo.String{Value: "some error"}}, ret) _, err = funcCall(uf) - assert.Equal(t, objects.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrWrongNumArguments, err) } func TestFuncAYRIE(t *testing.T) { uf := stdlib.FuncAYRIE(func(a []byte) (int, error) { return 5, nil }) - ret, err := funcCall(uf, &objects.Bytes{Value: []byte("foo")}) - assert.NoError(t, err) - assert.Equal(t, &objects.Int{Value: 5}, ret) - uf = stdlib.FuncAYRIE(func(a []byte) (int, error) { return 0, errors.New("some error") }) - ret, err = funcCall(uf, &objects.Bytes{Value: []byte("foo")}) - assert.NoError(t, err) - assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) + ret, err := funcCall(uf, &tengo.Bytes{Value: []byte("foo")}) + require.NoError(t, err) + require.Equal(t, &tengo.Int{Value: 5}, ret) + uf = stdlib.FuncAYRIE(func(a []byte) (int, error) { + return 0, errors.New("some error") + }) + ret, err = funcCall(uf, &tengo.Bytes{Value: []byte("foo")}) + require.NoError(t, err) + require.Equal(t, + &tengo.Error{Value: &tengo.String{Value: "some error"}}, ret) _, err = funcCall(uf) - assert.Equal(t, objects.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrWrongNumArguments, err) } func TestFuncASSRI(t *testing.T) { uf := stdlib.FuncASSRI(func(a, b string) int { return len(a) + len(b) }) - ret, err := funcCall(uf, &objects.String{Value: "foo"}, &objects.String{Value: "bar"}) - assert.NoError(t, err) - assert.Equal(t, &objects.Int{Value: 6}, ret) - _, err = funcCall(uf, &objects.String{Value: "foo"}) - assert.Equal(t, objects.ErrWrongNumArguments, err) + ret, err := funcCall(uf, + &tengo.String{Value: "foo"}, &tengo.String{Value: "bar"}) + require.NoError(t, err) + require.Equal(t, &tengo.Int{Value: 6}, ret) + _, err = funcCall(uf, &tengo.String{Value: "foo"}) + require.Equal(t, tengo.ErrWrongNumArguments, err) } func TestFuncASSRS(t *testing.T) { uf := stdlib.FuncASSRS(func(a, b string) string { return a + b }) - ret, err := funcCall(uf, &objects.String{Value: "foo"}, &objects.String{Value: "bar"}) - assert.NoError(t, err) - assert.Equal(t, &objects.String{Value: "foobar"}, ret) - _, err = funcCall(uf, &objects.String{Value: "foo"}) - assert.Equal(t, objects.ErrWrongNumArguments, err) + ret, err := funcCall(uf, + &tengo.String{Value: "foo"}, &tengo.String{Value: "bar"}) + require.NoError(t, err) + require.Equal(t, &tengo.String{Value: "foobar"}, ret) + _, err = funcCall(uf, &tengo.String{Value: "foo"}) + require.Equal(t, tengo.ErrWrongNumArguments, err) } func TestFuncASSRB(t *testing.T) { uf := stdlib.FuncASSRB(func(a, b string) bool { return len(a) > len(b) }) - ret, err := funcCall(uf, &objects.String{Value: "123"}, &objects.String{Value: "12"}) - assert.NoError(t, err) - assert.Equal(t, objects.TrueValue, ret) - _, err = funcCall(uf, &objects.String{Value: "foo"}) - assert.Equal(t, objects.ErrWrongNumArguments, err) + ret, err := funcCall(uf, + &tengo.String{Value: "123"}, &tengo.String{Value: "12"}) + require.NoError(t, err) + require.Equal(t, tengo.TrueValue, ret) + _, err = funcCall(uf, &tengo.String{Value: "foo"}) + require.Equal(t, tengo.ErrWrongNumArguments, err) } func TestFuncAIRS(t *testing.T) { uf := stdlib.FuncAIRS(func(a int) string { return strconv.Itoa(a) }) - ret, err := funcCall(uf, &objects.Int{Value: 55}) - assert.NoError(t, err) - assert.Equal(t, &objects.String{Value: "55"}, ret) + ret, err := funcCall(uf, &tengo.Int{Value: 55}) + require.NoError(t, err) + require.Equal(t, &tengo.String{Value: "55"}, ret) _, err = funcCall(uf) - assert.Equal(t, objects.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrWrongNumArguments, err) } func TestFuncAIRIs(t *testing.T) { uf := stdlib.FuncAIRIs(func(a int) []int { return []int{a, a} }) - ret, err := funcCall(uf, &objects.Int{Value: 55}) - assert.NoError(t, err) - assert.Equal(t, array(&objects.Int{Value: 55}, &objects.Int{Value: 55}), ret) + ret, err := funcCall(uf, &tengo.Int{Value: 55}) + require.NoError(t, err) + require.Equal(t, array(&tengo.Int{Value: 55}, &tengo.Int{Value: 55}), ret) _, err = funcCall(uf) - assert.Equal(t, objects.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrWrongNumArguments, err) } func TestFuncAI64R(t *testing.T) { uf := stdlib.FuncAIR(func(a int) {}) - ret, err := funcCall(uf, &objects.Int{Value: 55}) - assert.NoError(t, err) - assert.Equal(t, objects.UndefinedValue, ret) + ret, err := funcCall(uf, &tengo.Int{Value: 55}) + require.NoError(t, err) + require.Equal(t, tengo.UndefinedValue, ret) _, err = funcCall(uf) - assert.Equal(t, objects.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrWrongNumArguments, err) } func TestFuncARI64(t *testing.T) { uf := stdlib.FuncARI64(func() int64 { return 55 }) ret, err := funcCall(uf) - assert.NoError(t, err) - assert.Equal(t, &objects.Int{Value: 55}, ret) - _, err = funcCall(uf, &objects.Int{Value: 55}) - assert.Equal(t, objects.ErrWrongNumArguments, err) + require.NoError(t, err) + require.Equal(t, &tengo.Int{Value: 55}, ret) + _, err = funcCall(uf, &tengo.Int{Value: 55}) + require.Equal(t, tengo.ErrWrongNumArguments, err) } func TestFuncASsSRS(t *testing.T) { - uf := stdlib.FuncASsSRS(func(a []string, b string) string { return strings.Join(a, b) }) - ret, err := funcCall(uf, array(&objects.String{Value: "abc"}, &objects.String{Value: "def"}), &objects.String{Value: "-"}) - assert.NoError(t, err) - assert.Equal(t, &objects.String{Value: "abc-def"}, ret) + uf := stdlib.FuncASsSRS(func(a []string, b string) string { + return strings.Join(a, b) + }) + ret, err := funcCall(uf, + array(&tengo.String{Value: "abc"}, &tengo.String{Value: "def"}), + &tengo.String{Value: "-"}) + require.NoError(t, err) + require.Equal(t, &tengo.String{Value: "abc-def"}, ret) _, err = funcCall(uf) - assert.Equal(t, objects.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrWrongNumArguments, err) } func TestFuncAI64RI64(t *testing.T) { uf := stdlib.FuncAI64RI64(func(a int64) int64 { return a * 2 }) - ret, err := funcCall(uf, &objects.Int{Value: 55}) - assert.NoError(t, err) - assert.Equal(t, &objects.Int{Value: 110}, ret) + ret, err := funcCall(uf, &tengo.Int{Value: 55}) + require.NoError(t, err) + require.Equal(t, &tengo.Int{Value: 110}, ret) _, err = funcCall(uf) - assert.Equal(t, objects.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrWrongNumArguments, err) } -func funcCall(fn objects.CallableFunc, args ...objects.Object) (objects.Object, error) { - userFunc := &objects.UserFunction{Value: fn} +func funcCall( + fn tengo.CallableFunc, + args ...tengo.Object, +) (tengo.Object, error) { + userFunc := &tengo.UserFunction{Value: fn} return userFunc.Call(args...) } -func array(elements ...objects.Object) *objects.Array { - return &objects.Array{Value: elements} +func array(elements ...tengo.Object) *tengo.Array { + return &tengo.Array{Value: elements} } diff --git a/stdlib/gensrcmods.go b/stdlib/gensrcmods.go index fada66b..bdd057f 100644 --- a/stdlib/gensrcmods.go +++ b/stdlib/gensrcmods.go @@ -27,7 +27,8 @@ func main() { src, err := ioutil.ReadFile(file.Name()) if err != nil { - log.Fatalf("file '%s' read error: %s", file.Name(), err.Error()) + log.Fatalf("file '%s' read error: %s", + file.Name(), err.Error()) } modules[modName] = string(src) @@ -42,7 +43,8 @@ package stdlib // SourceModules are source type standard library modules. var SourceModules = map[string]string{` + "\n") for modName, modSrc := range modules { - out.WriteString("\t\"" + modName + "\": " + strconv.Quote(modSrc) + ",\n") + out.WriteString("\t\"" + modName + "\": " + + strconv.Quote(modSrc) + ",\n") } out.WriteString("}\n") diff --git a/stdlib/hex.go b/stdlib/hex.go index acc29e6..035456e 100644 --- a/stdlib/hex.go +++ b/stdlib/hex.go @@ -2,10 +2,11 @@ package stdlib import ( "encoding/hex" - "github.com/d5/tengo/objects" + + "github.com/d5/tengo" ) -var hexModule = map[string]objects.Object{ - "encode": &objects.UserFunction{Value: FuncAYRS(hex.EncodeToString)}, - "decode": &objects.UserFunction{Value: FuncASRYE(hex.DecodeString)}, +var hexModule = map[string]tengo.Object{ + "encode": &tengo.UserFunction{Value: FuncAYRS(hex.EncodeToString)}, + "decode": &tengo.UserFunction{Value: FuncASRYE(hex.DecodeString)}, } diff --git a/stdlib/hex_test.go b/stdlib/hex_test.go index f545a51..dad7674 100644 --- a/stdlib/hex_test.go +++ b/stdlib/hex_test.go @@ -2,10 +2,13 @@ package stdlib_test import "testing" -var hexBytes1 = []byte{0x06, 0xAC, 0x76, 0x1B, 0x1D, 0x6A, 0xFA, 0x9D, 0xB1, 0xA0} +var hexBytes1 = []byte{ + 0x06, 0xAC, 0x76, 0x1B, 0x1D, 0x6A, 0xFA, 0x9D, 0xB1, 0xA0, +} + const hex1 = "06ac761b1d6afa9db1a0" -func TestHex(t *testing.T) { +func TestHex(t *testing.T) { module(t, `hex`).call("encode", hexBytes1).expect(hex1) module(t, `hex`).call("decode", hex1).expect(hexBytes1) } diff --git a/stdlib/json.go b/stdlib/json.go index f913dc4..342c9c0 100644 --- a/stdlib/json.go +++ b/stdlib/json.go @@ -4,37 +4,53 @@ import ( "bytes" gojson "encoding/json" - "github.com/d5/tengo/objects" + "github.com/d5/tengo" "github.com/d5/tengo/stdlib/json" ) -var jsonModule = map[string]objects.Object{ - "decode": &objects.UserFunction{Name: "decode", Value: jsonDecode}, - "encode": &objects.UserFunction{Name: "encode", Value: jsonEncode}, - "indent": &objects.UserFunction{Name: "encode", Value: jsonIndent}, - "html_escape": &objects.UserFunction{Name: "html_escape", Value: jsonHTMLEscape}, +var jsonModule = map[string]tengo.Object{ + "decode": &tengo.UserFunction{ + Name: "decode", + Value: jsonDecode, + }, + "encode": &tengo.UserFunction{ + Name: "encode", + Value: jsonEncode, + }, + "indent": &tengo.UserFunction{ + Name: "encode", + Value: jsonIndent, + }, + "html_escape": &tengo.UserFunction{ + Name: "html_escape", + Value: jsonHTMLEscape, + }, } -func jsonDecode(args ...objects.Object) (ret objects.Object, err error) { +func jsonDecode(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 1 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } switch o := args[0].(type) { - case *objects.Bytes: + case *tengo.Bytes: v, err := json.Decode(o.Value) if err != nil { - return &objects.Error{Value: &objects.String{Value: err.Error()}}, nil + return &tengo.Error{ + Value: &tengo.String{Value: err.Error()}, + }, nil } return v, nil - case *objects.String: + case *tengo.String: v, err := json.Decode([]byte(o.Value)) if err != nil { - return &objects.Error{Value: &objects.String{Value: err.Error()}}, nil + return &tengo.Error{ + Value: &tengo.String{Value: err.Error()}, + }, nil } return v, nil default: - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "bytes/string", Found: args[0].TypeName(), @@ -42,36 +58,36 @@ func jsonDecode(args ...objects.Object) (ret objects.Object, err error) { } } -func jsonEncode(args ...objects.Object) (ret objects.Object, err error) { +func jsonEncode(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 1 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } b, err := json.Encode(args[0]) if err != nil { - return &objects.Error{Value: &objects.String{Value: err.Error()}}, nil + return &tengo.Error{Value: &tengo.String{Value: err.Error()}}, nil } - return &objects.Bytes{Value: b}, nil + return &tengo.Bytes{Value: b}, nil } -func jsonIndent(args ...objects.Object) (ret objects.Object, err error) { +func jsonIndent(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 3 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - prefix, ok := objects.ToString(args[1]) + prefix, ok := tengo.ToString(args[1]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "prefix", Expected: "string(compatible)", Found: args[1].TypeName(), } } - indent, ok := objects.ToString(args[2]) + indent, ok := tengo.ToString(args[2]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "indent", Expected: "string(compatible)", Found: args[2].TypeName(), @@ -79,22 +95,26 @@ func jsonIndent(args ...objects.Object) (ret objects.Object, err error) { } switch o := args[0].(type) { - case *objects.Bytes: + case *tengo.Bytes: var dst bytes.Buffer err := gojson.Indent(&dst, o.Value, prefix, indent) if err != nil { - return &objects.Error{Value: &objects.String{Value: err.Error()}}, nil + return &tengo.Error{ + Value: &tengo.String{Value: err.Error()}, + }, nil } - return &objects.Bytes{Value: dst.Bytes()}, nil - case *objects.String: + return &tengo.Bytes{Value: dst.Bytes()}, nil + case *tengo.String: var dst bytes.Buffer err := gojson.Indent(&dst, []byte(o.Value), prefix, indent) if err != nil { - return &objects.Error{Value: &objects.String{Value: err.Error()}}, nil + return &tengo.Error{ + Value: &tengo.String{Value: err.Error()}, + }, nil } - return &objects.Bytes{Value: dst.Bytes()}, nil + return &tengo.Bytes{Value: dst.Bytes()}, nil default: - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "bytes/string", Found: args[0].TypeName(), @@ -102,22 +122,22 @@ func jsonIndent(args ...objects.Object) (ret objects.Object, err error) { } } -func jsonHTMLEscape(args ...objects.Object) (ret objects.Object, err error) { +func jsonHTMLEscape(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 1 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } switch o := args[0].(type) { - case *objects.Bytes: + case *tengo.Bytes: var dst bytes.Buffer gojson.HTMLEscape(&dst, o.Value) - return &objects.Bytes{Value: dst.Bytes()}, nil - case *objects.String: + return &tengo.Bytes{Value: dst.Bytes()}, nil + case *tengo.String: var dst bytes.Buffer gojson.HTMLEscape(&dst, []byte(o.Value)) - return &objects.Bytes{Value: dst.Bytes()}, nil + return &tengo.Bytes{Value: dst.Bytes()}, nil default: - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "bytes/string", Found: args[0].TypeName(), diff --git a/stdlib/json/decode.go b/stdlib/json/decode.go index 5a3fe6c..98dad0e 100644 --- a/stdlib/json/decode.go +++ b/stdlib/json/decode.go @@ -12,21 +12,19 @@ import ( "unicode/utf16" "unicode/utf8" - "github.com/d5/tengo/objects" + "github.com/d5/tengo" ) // Decode parses the JSON-encoded data and returns the result object. -func Decode(data []byte) (objects.Object, error) { +func Decode(data []byte) (tengo.Object, error) { var d decodeState err := checkValid(data, &d.scan) if err != nil { return nil, err } - d.init(data) d.scan.reset() d.scanWhile(scanSkipSpace) - return d.value() } @@ -80,45 +78,37 @@ func (d *decodeState) scanWhile(op int) { d.opcode = d.scan.eof() } -func (d *decodeState) value() (objects.Object, error) { +func (d *decodeState) value() (tengo.Object, error) { switch d.opcode { default: panic(phasePanicMsg) - case scanBeginArray: o, err := d.array() if err != nil { return nil, err } - d.scanNext() - return o, nil - case scanBeginObject: o, err := d.object() if err != nil { return nil, err } - d.scanNext() - return o, nil - case scanBeginLiteral: return d.literal() } } -func (d *decodeState) array() (objects.Object, error) { - var arr []objects.Object +func (d *decodeState) array() (tengo.Object, error) { + var arr []tengo.Object for { // Look ahead for ] - can only happen on first iteration. d.scanWhile(scanSkipSpace) if d.opcode == scanEndArray { break } - o, err := d.value() if err != nil { return nil, err @@ -136,12 +126,11 @@ func (d *decodeState) array() (objects.Object, error) { panic(phasePanicMsg) } } - - return &objects.Array{Value: arr}, nil + return &tengo.Array{Value: arr}, nil } -func (d *decodeState) object() (objects.Object, error) { - m := make(map[string]objects.Object) +func (d *decodeState) object() (tengo.Object, error) { + m := make(map[string]tengo.Object) for { // Read opening " of string key or closing }. d.scanWhile(scanSkipSpace) @@ -190,11 +179,10 @@ func (d *decodeState) object() (objects.Object, error) { panic(phasePanicMsg) } } - - return &objects.Map{Value: m}, nil + return &tengo.Map{Value: m}, nil } -func (d *decodeState) literal() (objects.Object, error) { +func (d *decodeState) literal() (tengo.Object, error) { // All bytes inside literal return scanContinue op code. start := d.readIndex() d.scanWhile(scanContinue) @@ -203,28 +191,27 @@ func (d *decodeState) literal() (objects.Object, error) { switch c := item[0]; c { case 'n': // null - return objects.UndefinedValue, nil + return tengo.UndefinedValue, nil case 't', 'f': // true, false if c == 't' { - return objects.TrueValue, nil + return tengo.TrueValue, nil } - return objects.FalseValue, nil + return tengo.FalseValue, nil case '"': // string s, ok := unquote(item) if !ok { panic(phasePanicMsg) } - return &objects.String{Value: s}, nil + return &tengo.String{Value: s}, nil default: // number if c != '-' && (c < '0' || c > '9') { panic(phasePanicMsg) } - n, _ := strconv.ParseFloat(string(item), 10) - return &objects.Float{Value: n}, nil + return &tengo.Float{Value: n}, nil } } @@ -265,9 +252,8 @@ func unquoteBytes(s []byte) (t []byte, ok bool) { } s = s[1 : len(s)-1] - // Check for unusual characters. If there are none, - // then no unquoting is needed, so return a slice of the - // original bytes. + // Check for unusual characters. If there are none, then no unquoting is + // needed, so return a slice of the original bytes. r := 0 for r < len(s) { c := s[r] @@ -341,7 +327,8 @@ func unquoteBytes(s []byte) (t []byte, ok bool) { r += 6 if utf16.IsSurrogate(rr) { rr1 := getu4(s[r:]) - if dec := utf16.DecodeRune(rr, rr1); dec != unicode.ReplacementChar { + dec := utf16.DecodeRune(rr, rr1) + if dec != unicode.ReplacementChar { // A valid pair; consume. r += 6 w += utf8.EncodeRune(b[w:], dec) @@ -352,17 +339,14 @@ func unquoteBytes(s []byte) (t []byte, ok bool) { } w += utf8.EncodeRune(b[w:], rr) } - // Quote, control characters are invalid. case c == '"', c < ' ': return - // ASCII case c < utf8.RuneSelf: b[w] = c r++ w++ - // Coerce to well-formed UTF-8. default: rr, size := utf8.DecodeRune(s[r:]) diff --git a/stdlib/json/encode.go b/stdlib/json/encode.go index 2b8b17e..25d3b28 100644 --- a/stdlib/json/encode.go +++ b/stdlib/json/encode.go @@ -12,15 +12,15 @@ import ( "math" "strconv" - "github.com/d5/tengo/objects" + "github.com/d5/tengo" ) // Encode returns the JSON encoding of the object. -func Encode(o objects.Object) ([]byte, error) { +func Encode(o tengo.Object) ([]byte, error) { var b []byte switch o := o.(type) { - case *objects.Array: + case *tengo.Array: b = append(b, '[') len1 := len(o.Value) - 1 for idx, elem := range o.Value { @@ -34,7 +34,7 @@ func Encode(o objects.Object) ([]byte, error) { } } b = append(b, ']') - case *objects.ImmutableArray: + case *tengo.ImmutableArray: b = append(b, '[') len1 := len(o.Value) - 1 for idx, elem := range o.Value { @@ -48,7 +48,7 @@ func Encode(o objects.Object) ([]byte, error) { } } b = append(b, ']') - case *objects.Map: + case *tengo.Map: b = append(b, '{') len1 := len(o.Value) - 1 idx := 0 @@ -66,7 +66,7 @@ func Encode(o objects.Object) ([]byte, error) { idx++ } b = append(b, '}') - case *objects.ImmutableMap: + case *tengo.ImmutableMap: b = append(b, '{') len1 := len(o.Value) - 1 idx := 0 @@ -84,22 +84,22 @@ func Encode(o objects.Object) ([]byte, error) { idx++ } b = append(b, '}') - case *objects.Bool: + case *tengo.Bool: if o.IsFalsy() { b = strconv.AppendBool(b, false) } else { b = strconv.AppendBool(b, true) } - case *objects.Bytes: + case *tengo.Bytes: b = append(b, '"') encodedLen := base64.StdEncoding.EncodedLen(len(o.Value)) dst := make([]byte, encodedLen) base64.StdEncoding.Encode(dst, o.Value) b = append(b, dst...) b = append(b, '"') - case *objects.Char: + case *tengo.Char: b = strconv.AppendInt(b, int64(o.Value), 10) - case *objects.Float: + case *tengo.Float: var y []byte f := o.Value @@ -127,21 +127,20 @@ func Encode(o objects.Object) ([]byte, error) { } b = append(b, y...) - case *objects.Int: + case *tengo.Int: b = strconv.AppendInt(b, o.Value, 10) - case *objects.String: + case *tengo.String: b = strconv.AppendQuote(b, o.Value) - case *objects.Time: + case *tengo.Time: y, err := o.Value.MarshalJSON() if err != nil { return nil, err } b = append(b, y...) - case *objects.Undefined: + case *tengo.Undefined: b = append(b, "null"...) default: // unknown type: ignore } - return b, nil } diff --git a/stdlib/json/json_test.go b/stdlib/json/json_test.go index e20aed0..1a035a0 100644 --- a/stdlib/json/json_test.go +++ b/stdlib/json/json_test.go @@ -4,8 +4,8 @@ import ( gojson "encoding/json" "testing" - "github.com/d5/tengo/assert" - "github.com/d5/tengo/objects" + "github.com/d5/tengo" + "github.com/d5/tengo/internal/require" "github.com/d5/tengo/stdlib/json" ) @@ -38,16 +38,20 @@ func TestJSON(t *testing.T) { testJSONEncodeDecode(t, ARR{}) testJSONEncodeDecode(t, ARR{0}) testJSONEncodeDecode(t, ARR{false}) - testJSONEncodeDecode(t, ARR{1, 2, 3, "four", false}) - testJSONEncodeDecode(t, ARR{1, 2, 3, "four", false, MAP{"a": 0, "b": "bee", "bool": true}}) + testJSONEncodeDecode(t, ARR{1, 2, 3, + "four", false}) + testJSONEncodeDecode(t, ARR{1, 2, 3, + "four", false, MAP{"a": 0, "b": "bee", "bool": true}}) testJSONEncodeDecode(t, MAP{}) testJSONEncodeDecode(t, MAP{"a": 0}) testJSONEncodeDecode(t, MAP{"a": 0, "b": "bee"}) testJSONEncodeDecode(t, MAP{"a": 0, "b": "bee", "bool": true}) - testJSONEncodeDecode(t, MAP{"a": 0, "b": "bee", "arr": ARR{1, 2, 3, "four"}}) - testJSONEncodeDecode(t, MAP{"a": 0, "b": "bee", "arr": ARR{1, 2, 3, MAP{"a": false, "b": 109.4}}}) + testJSONEncodeDecode(t, MAP{"a": 0, "b": "bee", + "arr": ARR{1, 2, 3, "four"}}) + testJSONEncodeDecode(t, MAP{"a": 0, "b": "bee", + "arr": ARR{1, 2, 3, MAP{"a": false, "b": 109.4}}}) } func TestDecode(t *testing.T) { @@ -76,34 +80,24 @@ func TestDecode(t *testing.T) { func testDecodeError(t *testing.T, input string) { _, err := json.Decode([]byte(input)) - assert.Error(t, err) + require.Error(t, err) } -func testJSONEncodeDecode(t *testing.T, v interface{}) bool { - o, err := objects.FromInterface(v) - if !assert.NoError(t, err) { - return false - } +func testJSONEncodeDecode(t *testing.T, v interface{}) { + o, err := tengo.FromInterface(v) + require.NoError(t, err) b, err := json.Encode(o) - if !assert.NoError(t, err) { - return false - } + require.NoError(t, err) a, err := json.Decode(b) - if !assert.NoError(t, err, string(b)) { - return false - } + require.NoError(t, err, string(b)) vj, err := gojson.Marshal(v) - if !assert.NoError(t, err) { - return false - } + require.NoError(t, err) - aj, err := gojson.Marshal(objects.ToInterface(a)) - if !assert.NoError(t, err) { - return false - } + aj, err := gojson.Marshal(tengo.ToInterface(a)) + require.NoError(t, err) - return assert.Equal(t, vj, aj) + require.Equal(t, vj, aj) } diff --git a/stdlib/json/scanner.go b/stdlib/json/scanner.go index 8fc6776..aed15cf 100644 --- a/stdlib/json/scanner.go +++ b/stdlib/json/scanner.go @@ -532,14 +532,17 @@ func stateNul(s *scanner, c byte) int { // stateError is the state after reaching a syntax error, // such as after reading `[1}` or `5.1.2`. -func stateError(s *scanner, c byte) int { +func stateError(_ *scanner, _ byte) int { return scanError } // error records an error and switches to the error state. func (s *scanner) error(c byte, context string) int { s.step = stateError - s.err = &SyntaxError{"invalid character " + quoteChar(c) + " " + context, s.bytes} + s.err = &SyntaxError{ + msg: "invalid character " + quoteChar(c) + " " + context, + Offset: s.bytes, + } return scanError } diff --git a/stdlib/json_test.go b/stdlib/json_test.go index 08a0858..adf7037 100644 --- a/stdlib/json_test.go +++ b/stdlib/json_test.go @@ -3,35 +3,70 @@ package stdlib_test import "testing" func TestJSON(t *testing.T) { - module(t, "json").call("encode", 5).expect([]byte("5")) - module(t, "json").call("encode", "foobar").expect([]byte(`"foobar"`)) - module(t, "json").call("encode", MAP{"foo": 5}).expect([]byte("{\"foo\":5}")) - module(t, "json").call("encode", IMAP{"foo": 5}).expect([]byte("{\"foo\":5}")) - module(t, "json").call("encode", ARR{1, 2, 3}).expect([]byte("[1,2,3]")) - module(t, "json").call("encode", IARR{1, 2, 3}).expect([]byte("[1,2,3]")) - module(t, "json").call("encode", MAP{"foo": "bar"}).expect([]byte("{\"foo\":\"bar\"}")) - module(t, "json").call("encode", MAP{"foo": 1.8}).expect([]byte("{\"foo\":1.8}")) - module(t, "json").call("encode", MAP{"foo": true}).expect([]byte("{\"foo\":true}")) - module(t, "json").call("encode", MAP{"foo": '8'}).expect([]byte("{\"foo\":56}")) - module(t, "json").call("encode", MAP{"foo": []byte("foo")}).expect([]byte("{\"foo\":\"Zm9v\"}")) // json encoding returns []byte as base64 encoded string - module(t, "json").call("encode", MAP{"foo": ARR{"bar", 1, 1.8, '8', true}}).expect([]byte("{\"foo\":[\"bar\",1,1.8,56,true]}")) - module(t, "json").call("encode", MAP{"foo": IARR{"bar", 1, 1.8, '8', true}}).expect([]byte("{\"foo\":[\"bar\",1,1.8,56,true]}")) - module(t, "json").call("encode", MAP{"foo": ARR{ARR{"bar", 1}, ARR{"bar", 1}}}).expect([]byte("{\"foo\":[[\"bar\",1],[\"bar\",1]]}")) - module(t, "json").call("encode", MAP{"foo": MAP{"string": "bar"}}).expect([]byte("{\"foo\":{\"string\":\"bar\"}}")) - module(t, "json").call("encode", MAP{"foo": IMAP{"string": "bar"}}).expect([]byte("{\"foo\":{\"string\":\"bar\"}}")) - module(t, "json").call("encode", MAP{"foo": MAP{"map1": MAP{"string": "bar"}}}).expect([]byte("{\"foo\":{\"map1\":{\"string\":\"bar\"}}}")) - module(t, "json").call("encode", ARR{ARR{"bar", 1}, ARR{"bar", 1}}).expect([]byte("[[\"bar\",1],[\"bar\",1]]")) + module(t, "json").call("encode", 5). + expect([]byte("5")) + module(t, "json").call("encode", "foobar"). + expect([]byte(`"foobar"`)) + module(t, "json").call("encode", MAP{"foo": 5}). + expect([]byte("{\"foo\":5}")) + module(t, "json").call("encode", IMAP{"foo": 5}). + expect([]byte("{\"foo\":5}")) + module(t, "json").call("encode", ARR{1, 2, 3}). + expect([]byte("[1,2,3]")) + module(t, "json").call("encode", IARR{1, 2, 3}). + expect([]byte("[1,2,3]")) + module(t, "json").call("encode", MAP{"foo": "bar"}). + expect([]byte("{\"foo\":\"bar\"}")) + module(t, "json").call("encode", MAP{"foo": 1.8}). + expect([]byte("{\"foo\":1.8}")) + module(t, "json").call("encode", MAP{"foo": true}). + expect([]byte("{\"foo\":true}")) + module(t, "json").call("encode", MAP{"foo": '8'}). + expect([]byte("{\"foo\":56}")) + module(t, "json").call("encode", MAP{"foo": []byte("foo")}). + expect([]byte("{\"foo\":\"Zm9v\"}")) // json encoding returns []byte as base64 encoded string + module(t, "json"). + call("encode", MAP{"foo": ARR{"bar", 1, 1.8, '8', true}}). + expect([]byte("{\"foo\":[\"bar\",1,1.8,56,true]}")) + module(t, "json"). + call("encode", MAP{"foo": IARR{"bar", 1, 1.8, '8', true}}). + expect([]byte("{\"foo\":[\"bar\",1,1.8,56,true]}")) + module(t, "json"). + call("encode", MAP{"foo": ARR{ARR{"bar", 1}, ARR{"bar", 1}}}). + expect([]byte("{\"foo\":[[\"bar\",1],[\"bar\",1]]}")) + module(t, "json"). + call("encode", MAP{"foo": MAP{"string": "bar"}}). + expect([]byte("{\"foo\":{\"string\":\"bar\"}}")) + module(t, "json"). + call("encode", MAP{"foo": IMAP{"string": "bar"}}). + expect([]byte("{\"foo\":{\"string\":\"bar\"}}")) + module(t, "json"). + call("encode", MAP{"foo": MAP{"map1": MAP{"string": "bar"}}}). + expect([]byte("{\"foo\":{\"map1\":{\"string\":\"bar\"}}}")) + module(t, "json"). + call("encode", ARR{ARR{"bar", 1}, ARR{"bar", 1}}). + expect([]byte("[[\"bar\",1],[\"bar\",1]]")) - module(t, "json").call("decode", `5`).expect(5.0) - module(t, "json").call("decode", `"foo"`).expect("foo") - module(t, "json").call("decode", `[1,2,3,"bar"]`).expect(ARR{1.0, 2.0, 3.0, "bar"}) - module(t, "json").call("decode", `{"foo":5}`).expect(MAP{"foo": 5.0}) - module(t, "json").call("decode", `{"foo":2.5}`).expect(MAP{"foo": 2.5}) - module(t, "json").call("decode", `{"foo":true}`).expect(MAP{"foo": true}) - module(t, "json").call("decode", `{"foo":"bar"}`).expect(MAP{"foo": "bar"}) - module(t, "json").call("decode", `{"foo":[1,2,3,"bar"]}`).expect(MAP{"foo": ARR{1.0, 2.0, 3.0, "bar"}}) + module(t, "json").call("decode", `5`). + expect(5.0) + module(t, "json").call("decode", `"foo"`). + expect("foo") + module(t, "json").call("decode", `[1,2,3,"bar"]`). + expect(ARR{1.0, 2.0, 3.0, "bar"}) + module(t, "json").call("decode", `{"foo":5}`). + expect(MAP{"foo": 5.0}) + module(t, "json").call("decode", `{"foo":2.5}`). + expect(MAP{"foo": 2.5}) + module(t, "json").call("decode", `{"foo":true}`). + expect(MAP{"foo": true}) + module(t, "json").call("decode", `{"foo":"bar"}`). + expect(MAP{"foo": "bar"}) + module(t, "json").call("decode", `{"foo":[1,2,3,"bar"]}`). + expect(MAP{"foo": ARR{1.0, 2.0, 3.0, "bar"}}) - module(t, "json").call("indent", []byte("{\"foo\":[\"bar\",1,1.8,56,true]}"), "", " ").expect([]byte(`{ + module(t, "json"). + call("indent", []byte("{\"foo\":[\"bar\",1,1.8,56,true]}"), "", " "). + expect([]byte(`{ "foo": [ "bar", 1, @@ -41,5 +76,9 @@ func TestJSON(t *testing.T) { ] }`)) - module(t, "json").call("html_escape", []byte(`{"M":"foo &`+"\xe2\x80\xa8 \xe2\x80\xa9"+`"}`)).expect([]byte(`{"M":"\u003chtml\u003efoo \u0026\u2028 \u2029\u003c/html\u003e"}`)) + module(t, "json"). + call("html_escape", []byte( + `{"M":"foo &`+"\xe2\x80\xa8 \xe2\x80\xa9"+`"}`)). + expect([]byte( + `{"M":"\u003chtml\u003efoo \u0026\u2028 \u2029\u003c/html\u003e"}`)) } diff --git a/stdlib/math.go b/stdlib/math.go index 08d82bd..156b203 100644 --- a/stdlib/math.go +++ b/stdlib/math.go @@ -3,72 +3,231 @@ package stdlib import ( "math" - "github.com/d5/tengo/objects" + "github.com/d5/tengo" ) -var mathModule = map[string]objects.Object{ - "e": &objects.Float{Value: math.E}, - "pi": &objects.Float{Value: math.Pi}, - "phi": &objects.Float{Value: math.Phi}, - "sqrt2": &objects.Float{Value: math.Sqrt2}, - "sqrtE": &objects.Float{Value: math.SqrtE}, - "sqrtPi": &objects.Float{Value: math.SqrtPi}, - "sqrtPhi": &objects.Float{Value: math.SqrtPhi}, - "ln2": &objects.Float{Value: math.Ln2}, - "log2E": &objects.Float{Value: math.Log2E}, - "ln10": &objects.Float{Value: math.Ln10}, - "log10E": &objects.Float{Value: math.Log10E}, - "abs": &objects.UserFunction{Name: "abs", Value: FuncAFRF(math.Abs)}, - "acos": &objects.UserFunction{Name: "acos", Value: FuncAFRF(math.Acos)}, - "acosh": &objects.UserFunction{Name: "acosh", Value: FuncAFRF(math.Acosh)}, - "asin": &objects.UserFunction{Name: "asin", Value: FuncAFRF(math.Asin)}, - "asinh": &objects.UserFunction{Name: "asinh", Value: FuncAFRF(math.Asinh)}, - "atan": &objects.UserFunction{Name: "atan", Value: FuncAFRF(math.Atan)}, - "atan2": &objects.UserFunction{Name: "atan2", Value: FuncAFFRF(math.Atan2)}, - "atanh": &objects.UserFunction{Name: "atanh", Value: FuncAFRF(math.Atanh)}, - "cbrt": &objects.UserFunction{Name: "cbrt", Value: FuncAFRF(math.Cbrt)}, - "ceil": &objects.UserFunction{Name: "ceil", Value: FuncAFRF(math.Ceil)}, - "copysign": &objects.UserFunction{Name: "copysign", Value: FuncAFFRF(math.Copysign)}, - "cos": &objects.UserFunction{Name: "cos", Value: FuncAFRF(math.Cos)}, - "cosh": &objects.UserFunction{Name: "cosh", Value: FuncAFRF(math.Cosh)}, - "dim": &objects.UserFunction{Name: "dim", Value: FuncAFFRF(math.Dim)}, - "erf": &objects.UserFunction{Name: "erf", Value: FuncAFRF(math.Erf)}, - "erfc": &objects.UserFunction{Name: "erfc", Value: FuncAFRF(math.Erfc)}, - "exp": &objects.UserFunction{Name: "exp", Value: FuncAFRF(math.Exp)}, - "exp2": &objects.UserFunction{Name: "exp2", Value: FuncAFRF(math.Exp2)}, - "expm1": &objects.UserFunction{Name: "expm1", Value: FuncAFRF(math.Expm1)}, - "floor": &objects.UserFunction{Name: "floor", Value: FuncAFRF(math.Floor)}, - "gamma": &objects.UserFunction{Name: "gamma", Value: FuncAFRF(math.Gamma)}, - "hypot": &objects.UserFunction{Name: "hypot", Value: FuncAFFRF(math.Hypot)}, - "ilogb": &objects.UserFunction{Name: "ilogb", Value: FuncAFRI(math.Ilogb)}, - "inf": &objects.UserFunction{Name: "inf", Value: FuncAIRF(math.Inf)}, - "is_inf": &objects.UserFunction{Name: "is_inf", Value: FuncAFIRB(math.IsInf)}, - "is_nan": &objects.UserFunction{Name: "is_nan", Value: FuncAFRB(math.IsNaN)}, - "j0": &objects.UserFunction{Name: "j0", Value: FuncAFRF(math.J0)}, - "j1": &objects.UserFunction{Name: "j1", Value: FuncAFRF(math.J1)}, - "jn": &objects.UserFunction{Name: "jn", Value: FuncAIFRF(math.Jn)}, - "ldexp": &objects.UserFunction{Name: "ldexp", Value: FuncAFIRF(math.Ldexp)}, - "log": &objects.UserFunction{Name: "log", Value: FuncAFRF(math.Log)}, - "log10": &objects.UserFunction{Name: "log10", Value: FuncAFRF(math.Log10)}, - "log1p": &objects.UserFunction{Name: "log1p", Value: FuncAFRF(math.Log1p)}, - "log2": &objects.UserFunction{Name: "log2", Value: FuncAFRF(math.Log2)}, - "logb": &objects.UserFunction{Name: "logb", Value: FuncAFRF(math.Logb)}, - "max": &objects.UserFunction{Name: "max", Value: FuncAFFRF(math.Max)}, - "min": &objects.UserFunction{Name: "min", Value: FuncAFFRF(math.Min)}, - "mod": &objects.UserFunction{Name: "mod", Value: FuncAFFRF(math.Mod)}, - "nan": &objects.UserFunction{Name: "nan", Value: FuncARF(math.NaN)}, - "nextafter": &objects.UserFunction{Name: "nextafter", Value: FuncAFFRF(math.Nextafter)}, - "pow": &objects.UserFunction{Name: "pow", Value: FuncAFFRF(math.Pow)}, - "pow10": &objects.UserFunction{Name: "pow10", Value: FuncAIRF(math.Pow10)}, - "remainder": &objects.UserFunction{Name: "remainder", Value: FuncAFFRF(math.Remainder)}, - "signbit": &objects.UserFunction{Name: "signbit", Value: FuncAFRB(math.Signbit)}, - "sin": &objects.UserFunction{Name: "sin", Value: FuncAFRF(math.Sin)}, - "sinh": &objects.UserFunction{Name: "sinh", Value: FuncAFRF(math.Sinh)}, - "sqrt": &objects.UserFunction{Name: "sqrt", Value: FuncAFRF(math.Sqrt)}, - "tan": &objects.UserFunction{Name: "tan", Value: FuncAFRF(math.Tan)}, - "tanh": &objects.UserFunction{Name: "tanh", Value: FuncAFRF(math.Tanh)}, - "trunc": &objects.UserFunction{Name: "trunc", Value: FuncAFRF(math.Trunc)}, - "y0": &objects.UserFunction{Name: "y0", Value: FuncAFRF(math.Y0)}, - "y1": &objects.UserFunction{Name: "y1", Value: FuncAFRF(math.Y1)}, - "yn": &objects.UserFunction{Name: "yn", Value: FuncAIFRF(math.Yn)}, +var mathModule = map[string]tengo.Object{ + "e": &tengo.Float{Value: math.E}, + "pi": &tengo.Float{Value: math.Pi}, + "phi": &tengo.Float{Value: math.Phi}, + "sqrt2": &tengo.Float{Value: math.Sqrt2}, + "sqrtE": &tengo.Float{Value: math.SqrtE}, + "sqrtPi": &tengo.Float{Value: math.SqrtPi}, + "sqrtPhi": &tengo.Float{Value: math.SqrtPhi}, + "ln2": &tengo.Float{Value: math.Ln2}, + "log2E": &tengo.Float{Value: math.Log2E}, + "ln10": &tengo.Float{Value: math.Ln10}, + "log10E": &tengo.Float{Value: math.Log10E}, + "abs": &tengo.UserFunction{ + Name: "abs", + Value: FuncAFRF(math.Abs), + }, + "acos": &tengo.UserFunction{ + Name: "acos", + Value: FuncAFRF(math.Acos), + }, + "acosh": &tengo.UserFunction{ + Name: "acosh", + Value: FuncAFRF(math.Acosh), + }, + "asin": &tengo.UserFunction{ + Name: "asin", + Value: FuncAFRF(math.Asin), + }, + "asinh": &tengo.UserFunction{ + Name: "asinh", + Value: FuncAFRF(math.Asinh), + }, + "atan": &tengo.UserFunction{ + Name: "atan", + Value: FuncAFRF(math.Atan), + }, + "atan2": &tengo.UserFunction{ + Name: "atan2", + Value: FuncAFFRF(math.Atan2), + }, + "atanh": &tengo.UserFunction{ + Name: "atanh", + Value: FuncAFRF(math.Atanh), + }, + "cbrt": &tengo.UserFunction{ + Name: "cbrt", + Value: FuncAFRF(math.Cbrt), + }, + "ceil": &tengo.UserFunction{ + Name: "ceil", + Value: FuncAFRF(math.Ceil), + }, + "copysign": &tengo.UserFunction{ + Name: "copysign", + Value: FuncAFFRF(math.Copysign), + }, + "cos": &tengo.UserFunction{ + Name: "cos", + Value: FuncAFRF(math.Cos), + }, + "cosh": &tengo.UserFunction{ + Name: "cosh", + Value: FuncAFRF(math.Cosh), + }, + "dim": &tengo.UserFunction{ + Name: "dim", + Value: FuncAFFRF(math.Dim), + }, + "erf": &tengo.UserFunction{ + Name: "erf", + Value: FuncAFRF(math.Erf), + }, + "erfc": &tengo.UserFunction{ + Name: "erfc", + Value: FuncAFRF(math.Erfc), + }, + "exp": &tengo.UserFunction{ + Name: "exp", + Value: FuncAFRF(math.Exp), + }, + "exp2": &tengo.UserFunction{ + Name: "exp2", + Value: FuncAFRF(math.Exp2), + }, + "expm1": &tengo.UserFunction{ + Name: "expm1", + Value: FuncAFRF(math.Expm1), + }, + "floor": &tengo.UserFunction{ + Name: "floor", + Value: FuncAFRF(math.Floor), + }, + "gamma": &tengo.UserFunction{ + Name: "gamma", + Value: FuncAFRF(math.Gamma), + }, + "hypot": &tengo.UserFunction{ + Name: "hypot", + Value: FuncAFFRF(math.Hypot), + }, + "ilogb": &tengo.UserFunction{ + Name: "ilogb", + Value: FuncAFRI(math.Ilogb), + }, + "inf": &tengo.UserFunction{ + Name: "inf", + Value: FuncAIRF(math.Inf), + }, + "is_inf": &tengo.UserFunction{ + Name: "is_inf", + Value: FuncAFIRB(math.IsInf), + }, + "is_nan": &tengo.UserFunction{ + Name: "is_nan", + Value: FuncAFRB(math.IsNaN), + }, + "j0": &tengo.UserFunction{ + Name: "j0", + Value: FuncAFRF(math.J0), + }, + "j1": &tengo.UserFunction{ + Name: "j1", + Value: FuncAFRF(math.J1), + }, + "jn": &tengo.UserFunction{ + Name: "jn", + Value: FuncAIFRF(math.Jn), + }, + "ldexp": &tengo.UserFunction{ + Name: "ldexp", + Value: FuncAFIRF(math.Ldexp), + }, + "log": &tengo.UserFunction{ + Name: "log", + Value: FuncAFRF(math.Log), + }, + "log10": &tengo.UserFunction{ + Name: "log10", + Value: FuncAFRF(math.Log10), + }, + "log1p": &tengo.UserFunction{ + Name: "log1p", + Value: FuncAFRF(math.Log1p), + }, + "log2": &tengo.UserFunction{ + Name: "log2", + Value: FuncAFRF(math.Log2), + }, + "logb": &tengo.UserFunction{ + Name: "logb", + Value: FuncAFRF(math.Logb), + }, + "max": &tengo.UserFunction{ + Name: "max", + Value: FuncAFFRF(math.Max), + }, + "min": &tengo.UserFunction{ + Name: "min", + Value: FuncAFFRF(math.Min), + }, + "mod": &tengo.UserFunction{ + Name: "mod", + Value: FuncAFFRF(math.Mod), + }, + "nan": &tengo.UserFunction{ + Name: "nan", + Value: FuncARF(math.NaN), + }, + "nextafter": &tengo.UserFunction{ + Name: "nextafter", + Value: FuncAFFRF(math.Nextafter), + }, + "pow": &tengo.UserFunction{ + Name: "pow", + Value: FuncAFFRF(math.Pow), + }, + "pow10": &tengo.UserFunction{ + Name: "pow10", + Value: FuncAIRF(math.Pow10), + }, + "remainder": &tengo.UserFunction{ + Name: "remainder", + Value: FuncAFFRF(math.Remainder), + }, + "signbit": &tengo.UserFunction{ + Name: "signbit", + Value: FuncAFRB(math.Signbit), + }, + "sin": &tengo.UserFunction{ + Name: "sin", + Value: FuncAFRF(math.Sin), + }, + "sinh": &tengo.UserFunction{ + Name: "sinh", + Value: FuncAFRF(math.Sinh), + }, + "sqrt": &tengo.UserFunction{ + Name: "sqrt", + Value: FuncAFRF(math.Sqrt), + }, + "tan": &tengo.UserFunction{ + Name: "tan", + Value: FuncAFRF(math.Tan), + }, + "tanh": &tengo.UserFunction{ + Name: "tanh", + Value: FuncAFRF(math.Tanh), + }, + "trunc": &tengo.UserFunction{ + Name: "trunc", + Value: FuncAFRF(math.Trunc), + }, + "y0": &tengo.UserFunction{ + Name: "y0", + Value: FuncAFRF(math.Y0), + }, + "y1": &tengo.UserFunction{ + Name: "y1", + Value: FuncAFRF(math.Y1), + }, + "yn": &tengo.UserFunction{ + Name: "yn", + Value: FuncAIFRF(math.Yn), + }, } diff --git a/stdlib/os.go b/stdlib/os.go index a7890cc..88bd041 100644 --- a/stdlib/os.go +++ b/stdlib/os.go @@ -8,323 +8,410 @@ import ( "os/exec" "github.com/d5/tengo" - "github.com/d5/tengo/objects" ) -var osModule = map[string]objects.Object{ - "o_rdonly": &objects.Int{Value: int64(os.O_RDONLY)}, - "o_wronly": &objects.Int{Value: int64(os.O_WRONLY)}, - "o_rdwr": &objects.Int{Value: int64(os.O_RDWR)}, - "o_append": &objects.Int{Value: int64(os.O_APPEND)}, - "o_create": &objects.Int{Value: int64(os.O_CREATE)}, - "o_excl": &objects.Int{Value: int64(os.O_EXCL)}, - "o_sync": &objects.Int{Value: int64(os.O_SYNC)}, - "o_trunc": &objects.Int{Value: int64(os.O_TRUNC)}, - "mode_dir": &objects.Int{Value: int64(os.ModeDir)}, - "mode_append": &objects.Int{Value: int64(os.ModeAppend)}, - "mode_exclusive": &objects.Int{Value: int64(os.ModeExclusive)}, - "mode_temporary": &objects.Int{Value: int64(os.ModeTemporary)}, - "mode_symlink": &objects.Int{Value: int64(os.ModeSymlink)}, - "mode_device": &objects.Int{Value: int64(os.ModeDevice)}, - "mode_named_pipe": &objects.Int{Value: int64(os.ModeNamedPipe)}, - "mode_socket": &objects.Int{Value: int64(os.ModeSocket)}, - "mode_setuid": &objects.Int{Value: int64(os.ModeSetuid)}, - "mode_setgui": &objects.Int{Value: int64(os.ModeSetgid)}, - "mode_char_device": &objects.Int{Value: int64(os.ModeCharDevice)}, - "mode_sticky": &objects.Int{Value: int64(os.ModeSticky)}, - "mode_type": &objects.Int{Value: int64(os.ModeType)}, - "mode_perm": &objects.Int{Value: int64(os.ModePerm)}, - "path_separator": &objects.Char{Value: os.PathSeparator}, - "path_list_separator": &objects.Char{Value: os.PathListSeparator}, - "dev_null": &objects.String{Value: os.DevNull}, - "seek_set": &objects.Int{Value: int64(io.SeekStart)}, - "seek_cur": &objects.Int{Value: int64(io.SeekCurrent)}, - "seek_end": &objects.Int{Value: int64(io.SeekEnd)}, - "args": &objects.UserFunction{Name: "args", Value: osArgs}, // args() => array(string) - "chdir": &objects.UserFunction{Name: "chdir", Value: FuncASRE(os.Chdir)}, // chdir(dir string) => error - "chmod": osFuncASFmRE("chmod", os.Chmod), // chmod(name string, mode int) => error - "chown": &objects.UserFunction{Name: "chown", Value: FuncASIIRE(os.Chown)}, // chown(name string, uid int, gid int) => error - "clearenv": &objects.UserFunction{Name: "clearenv", Value: FuncAR(os.Clearenv)}, // clearenv() - "environ": &objects.UserFunction{Name: "environ", Value: FuncARSs(os.Environ)}, // environ() => array(string) - "exit": &objects.UserFunction{Name: "exit", Value: FuncAIR(os.Exit)}, // exit(code int) - "expand_env": &objects.UserFunction{Name: "expand_env", Value: osExpandEnv}, // expand_env(s string) => string - "getegid": &objects.UserFunction{Name: "getegid", Value: FuncARI(os.Getegid)}, // getegid() => int - "getenv": &objects.UserFunction{Name: "getenv", Value: FuncASRS(os.Getenv)}, // getenv(s string) => string - "geteuid": &objects.UserFunction{Name: "geteuid", Value: FuncARI(os.Geteuid)}, // geteuid() => int - "getgid": &objects.UserFunction{Name: "getgid", Value: FuncARI(os.Getgid)}, // getgid() => int - "getgroups": &objects.UserFunction{Name: "getgroups", Value: FuncARIsE(os.Getgroups)}, // getgroups() => array(string)/error - "getpagesize": &objects.UserFunction{Name: "getpagesize", Value: FuncARI(os.Getpagesize)}, // getpagesize() => int - "getpid": &objects.UserFunction{Name: "getpid", Value: FuncARI(os.Getpid)}, // getpid() => int - "getppid": &objects.UserFunction{Name: "getppid", Value: FuncARI(os.Getppid)}, // getppid() => int - "getuid": &objects.UserFunction{Name: "getuid", Value: FuncARI(os.Getuid)}, // getuid() => int - "getwd": &objects.UserFunction{Name: "getwd", Value: FuncARSE(os.Getwd)}, // getwd() => string/error - "hostname": &objects.UserFunction{Name: "hostname", Value: FuncARSE(os.Hostname)}, // hostname() => string/error - "lchown": &objects.UserFunction{Name: "lchown", Value: FuncASIIRE(os.Lchown)}, // lchown(name string, uid int, gid int) => error - "link": &objects.UserFunction{Name: "link", Value: FuncASSRE(os.Link)}, // link(oldname string, newname string) => error - "lookup_env": &objects.UserFunction{Name: "lookup_env", Value: osLookupEnv}, // lookup_env(key string) => string/false - "mkdir": osFuncASFmRE("mkdir", os.Mkdir), // mkdir(name string, perm int) => error - "mkdir_all": osFuncASFmRE("mkdir_all", os.MkdirAll), // mkdir_all(name string, perm int) => error - "readlink": &objects.UserFunction{Name: "readlink", Value: FuncASRSE(os.Readlink)}, // readlink(name string) => string/error - "remove": &objects.UserFunction{Name: "remove", Value: FuncASRE(os.Remove)}, // remove(name string) => error - "remove_all": &objects.UserFunction{Name: "remove_all", Value: FuncASRE(os.RemoveAll)}, // remove_all(name string) => error - "rename": &objects.UserFunction{Name: "rename", Value: FuncASSRE(os.Rename)}, // rename(oldpath string, newpath string) => error - "setenv": &objects.UserFunction{Name: "setenv", Value: FuncASSRE(os.Setenv)}, // setenv(key string, value string) => error - "symlink": &objects.UserFunction{Name: "symlink", Value: FuncASSRE(os.Symlink)}, // symlink(oldname string newname string) => error - "temp_dir": &objects.UserFunction{Name: "temp_dir", Value: FuncARS(os.TempDir)}, // temp_dir() => string - "truncate": &objects.UserFunction{Name: "truncate", Value: FuncASI64RE(os.Truncate)}, // truncate(name string, size int) => error - "unsetenv": &objects.UserFunction{Name: "unsetenv", Value: FuncASRE(os.Unsetenv)}, // unsetenv(key string) => error - "create": &objects.UserFunction{Name: "create", Value: osCreate}, // create(name string) => imap(file)/error - "open": &objects.UserFunction{Name: "open", Value: osOpen}, // open(name string) => imap(file)/error - "open_file": &objects.UserFunction{Name: "open_file", Value: osOpenFile}, // open_file(name string, flag int, perm int) => imap(file)/error - "find_process": &objects.UserFunction{Name: "find_process", Value: osFindProcess}, // find_process(pid int) => imap(process)/error - "start_process": &objects.UserFunction{Name: "start_process", Value: osStartProcess}, // start_process(name string, argv array(string), dir string, env array(string)) => imap(process)/error - "exec_look_path": &objects.UserFunction{Name: "exec_look_path", Value: FuncASRSE(exec.LookPath)}, // exec_look_path(file) => string/error - "exec": &objects.UserFunction{Name: "exec", Value: osExec}, // exec(name, args...) => command - "stat": &objects.UserFunction{Name: "stat", Value: osStat}, // stat(name) => imap(fileinfo)/error - "read_file": &objects.UserFunction{Name: "read_file", Value: osReadFile}, // readfile(name) => array(byte)/error +var osModule = map[string]tengo.Object{ + "o_rdonly": &tengo.Int{Value: int64(os.O_RDONLY)}, + "o_wronly": &tengo.Int{Value: int64(os.O_WRONLY)}, + "o_rdwr": &tengo.Int{Value: int64(os.O_RDWR)}, + "o_append": &tengo.Int{Value: int64(os.O_APPEND)}, + "o_create": &tengo.Int{Value: int64(os.O_CREATE)}, + "o_excl": &tengo.Int{Value: int64(os.O_EXCL)}, + "o_sync": &tengo.Int{Value: int64(os.O_SYNC)}, + "o_trunc": &tengo.Int{Value: int64(os.O_TRUNC)}, + "mode_dir": &tengo.Int{Value: int64(os.ModeDir)}, + "mode_append": &tengo.Int{Value: int64(os.ModeAppend)}, + "mode_exclusive": &tengo.Int{Value: int64(os.ModeExclusive)}, + "mode_temporary": &tengo.Int{Value: int64(os.ModeTemporary)}, + "mode_symlink": &tengo.Int{Value: int64(os.ModeSymlink)}, + "mode_device": &tengo.Int{Value: int64(os.ModeDevice)}, + "mode_named_pipe": &tengo.Int{Value: int64(os.ModeNamedPipe)}, + "mode_socket": &tengo.Int{Value: int64(os.ModeSocket)}, + "mode_setuid": &tengo.Int{Value: int64(os.ModeSetuid)}, + "mode_setgui": &tengo.Int{Value: int64(os.ModeSetgid)}, + "mode_char_device": &tengo.Int{Value: int64(os.ModeCharDevice)}, + "mode_sticky": &tengo.Int{Value: int64(os.ModeSticky)}, + "mode_type": &tengo.Int{Value: int64(os.ModeType)}, + "mode_perm": &tengo.Int{Value: int64(os.ModePerm)}, + "path_separator": &tengo.Char{Value: os.PathSeparator}, + "path_list_separator": &tengo.Char{Value: os.PathListSeparator}, + "dev_null": &tengo.String{Value: os.DevNull}, + "seek_set": &tengo.Int{Value: int64(io.SeekStart)}, + "seek_cur": &tengo.Int{Value: int64(io.SeekCurrent)}, + "seek_end": &tengo.Int{Value: int64(io.SeekEnd)}, + "args": &tengo.UserFunction{ + Name: "args", + Value: osArgs, + }, // args() => array(string) + "chdir": &tengo.UserFunction{ + Name: "chdir", + Value: FuncASRE(os.Chdir), + }, // chdir(dir string) => error + "chmod": osFuncASFmRE("chmod", os.Chmod), // chmod(name string, mode int) => error + "chown": &tengo.UserFunction{ + Name: "chown", + Value: FuncASIIRE(os.Chown), + }, // chown(name string, uid int, gid int) => error + "clearenv": &tengo.UserFunction{ + Name: "clearenv", + Value: FuncAR(os.Clearenv), + }, // clearenv() + "environ": &tengo.UserFunction{ + Name: "environ", + Value: FuncARSs(os.Environ), + }, // environ() => array(string) + "exit": &tengo.UserFunction{ + Name: "exit", + Value: FuncAIR(os.Exit), + }, // exit(code int) + "expand_env": &tengo.UserFunction{ + Name: "expand_env", + Value: osExpandEnv, + }, // expand_env(s string) => string + "getegid": &tengo.UserFunction{ + Name: "getegid", + Value: FuncARI(os.Getegid), + }, // getegid() => int + "getenv": &tengo.UserFunction{ + Name: "getenv", + Value: FuncASRS(os.Getenv), + }, // getenv(s string) => string + "geteuid": &tengo.UserFunction{ + Name: "geteuid", + Value: FuncARI(os.Geteuid), + }, // geteuid() => int + "getgid": &tengo.UserFunction{ + Name: "getgid", + Value: FuncARI(os.Getgid), + }, // getgid() => int + "getgroups": &tengo.UserFunction{ + Name: "getgroups", + Value: FuncARIsE(os.Getgroups), + }, // getgroups() => array(string)/error + "getpagesize": &tengo.UserFunction{ + Name: "getpagesize", + Value: FuncARI(os.Getpagesize), + }, // getpagesize() => int + "getpid": &tengo.UserFunction{ + Name: "getpid", + Value: FuncARI(os.Getpid), + }, // getpid() => int + "getppid": &tengo.UserFunction{ + Name: "getppid", + Value: FuncARI(os.Getppid), + }, // getppid() => int + "getuid": &tengo.UserFunction{ + Name: "getuid", + Value: FuncARI(os.Getuid), + }, // getuid() => int + "getwd": &tengo.UserFunction{ + Name: "getwd", + Value: FuncARSE(os.Getwd), + }, // getwd() => string/error + "hostname": &tengo.UserFunction{ + Name: "hostname", + Value: FuncARSE(os.Hostname), + }, // hostname() => string/error + "lchown": &tengo.UserFunction{ + Name: "lchown", + Value: FuncASIIRE(os.Lchown), + }, // lchown(name string, uid int, gid int) => error + "link": &tengo.UserFunction{ + Name: "link", + Value: FuncASSRE(os.Link), + }, // link(oldname string, newname string) => error + "lookup_env": &tengo.UserFunction{ + Name: "lookup_env", + Value: osLookupEnv, + }, // lookup_env(key string) => string/false + "mkdir": osFuncASFmRE("mkdir", os.Mkdir), // mkdir(name string, perm int) => error + "mkdir_all": osFuncASFmRE("mkdir_all", os.MkdirAll), // mkdir_all(name string, perm int) => error + "readlink": &tengo.UserFunction{ + Name: "readlink", + Value: FuncASRSE(os.Readlink), + }, // readlink(name string) => string/error + "remove": &tengo.UserFunction{ + Name: "remove", + Value: FuncASRE(os.Remove), + }, // remove(name string) => error + "remove_all": &tengo.UserFunction{ + Name: "remove_all", + Value: FuncASRE(os.RemoveAll), + }, // remove_all(name string) => error + "rename": &tengo.UserFunction{ + Name: "rename", + Value: FuncASSRE(os.Rename), + }, // rename(oldpath string, newpath string) => error + "setenv": &tengo.UserFunction{ + Name: "setenv", + Value: FuncASSRE(os.Setenv), + }, // setenv(key string, value string) => error + "symlink": &tengo.UserFunction{ + Name: "symlink", + Value: FuncASSRE(os.Symlink), + }, // symlink(oldname string newname string) => error + "temp_dir": &tengo.UserFunction{ + Name: "temp_dir", + Value: FuncARS(os.TempDir), + }, // temp_dir() => string + "truncate": &tengo.UserFunction{ + Name: "truncate", + Value: FuncASI64RE(os.Truncate), + }, // truncate(name string, size int) => error + "unsetenv": &tengo.UserFunction{ + Name: "unsetenv", + Value: FuncASRE(os.Unsetenv), + }, // unsetenv(key string) => error + "create": &tengo.UserFunction{ + Name: "create", + Value: osCreate, + }, // create(name string) => imap(file)/error + "open": &tengo.UserFunction{ + Name: "open", + Value: osOpen, + }, // open(name string) => imap(file)/error + "open_file": &tengo.UserFunction{ + Name: "open_file", + Value: osOpenFile, + }, // open_file(name string, flag int, perm int) => imap(file)/error + "find_process": &tengo.UserFunction{ + Name: "find_process", + Value: osFindProcess, + }, // find_process(pid int) => imap(process)/error + "start_process": &tengo.UserFunction{ + Name: "start_process", + Value: osStartProcess, + }, // start_process(name string, argv array(string), dir string, env array(string)) => imap(process)/error + "exec_look_path": &tengo.UserFunction{ + Name: "exec_look_path", + Value: FuncASRSE(exec.LookPath), + }, // exec_look_path(file) => string/error + "exec": &tengo.UserFunction{ + Name: "exec", + Value: osExec, + }, // exec(name, args...) => command + "stat": &tengo.UserFunction{ + Name: "stat", + Value: osStat, + }, // stat(name) => imap(fileinfo)/error + "read_file": &tengo.UserFunction{ + Name: "read_file", + Value: osReadFile, + }, // readfile(name) => array(byte)/error } -func osReadFile(args ...objects.Object) (ret objects.Object, err error) { +func osReadFile(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 1 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - fname, ok := objects.ToString(args[0]) + fname, ok := tengo.ToString(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), } } - bytes, err := ioutil.ReadFile(fname) if err != nil { return wrapError(err), nil } - if len(bytes) > tengo.MaxBytesLen { - return nil, objects.ErrBytesLimit + return nil, tengo.ErrBytesLimit } - - return &objects.Bytes{Value: bytes}, nil + return &tengo.Bytes{Value: bytes}, nil } -func osStat(args ...objects.Object) (ret objects.Object, err error) { +func osStat(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 1 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - fname, ok := objects.ToString(args[0]) + fname, ok := tengo.ToString(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), } } - stat, err := os.Stat(fname) if err != nil { return wrapError(err), nil } - - fstat := &objects.ImmutableMap{ - Value: map[string]objects.Object{ - "name": &objects.String{Value: stat.Name()}, - "mtime": &objects.Time{Value: stat.ModTime()}, - "size": &objects.Int{Value: stat.Size()}, - "mode": &objects.Int{Value: int64(stat.Mode())}, + fstat := &tengo.ImmutableMap{ + Value: map[string]tengo.Object{ + "name": &tengo.String{Value: stat.Name()}, + "mtime": &tengo.Time{Value: stat.ModTime()}, + "size": &tengo.Int{Value: stat.Size()}, + "mode": &tengo.Int{Value: int64(stat.Mode())}, }, } - if stat.IsDir() { - fstat.Value["directory"] = objects.TrueValue + fstat.Value["directory"] = tengo.TrueValue } else { - fstat.Value["directory"] = objects.FalseValue + fstat.Value["directory"] = tengo.FalseValue } - return fstat, nil } -func osCreate(args ...objects.Object) (objects.Object, error) { +func osCreate(args ...tengo.Object) (tengo.Object, error) { if len(args) != 1 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - s1, ok := objects.ToString(args[0]) + s1, ok := tengo.ToString(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), } } - res, err := os.Create(s1) if err != nil { return wrapError(err), nil } - return makeOSFile(res), nil } -func osOpen(args ...objects.Object) (objects.Object, error) { +func osOpen(args ...tengo.Object) (tengo.Object, error) { if len(args) != 1 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - s1, ok := objects.ToString(args[0]) + s1, ok := tengo.ToString(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), } } - res, err := os.Open(s1) if err != nil { return wrapError(err), nil } - return makeOSFile(res), nil } -func osOpenFile(args ...objects.Object) (objects.Object, error) { +func osOpenFile(args ...tengo.Object) (tengo.Object, error) { if len(args) != 3 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - s1, ok := objects.ToString(args[0]) + s1, ok := tengo.ToString(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), } } - - i2, ok := objects.ToInt(args[1]) + i2, ok := tengo.ToInt(args[1]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "second", Expected: "int(compatible)", Found: args[1].TypeName(), } } - - i3, ok := objects.ToInt(args[2]) + i3, ok := tengo.ToInt(args[2]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "third", Expected: "int(compatible)", Found: args[2].TypeName(), } } - res, err := os.OpenFile(s1, i2, os.FileMode(i3)) if err != nil { return wrapError(err), nil } - return makeOSFile(res), nil } -func osArgs(args ...objects.Object) (objects.Object, error) { +func osArgs(args ...tengo.Object) (tengo.Object, error) { if len(args) != 0 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - arr := &objects.Array{} + arr := &tengo.Array{} for _, osArg := range os.Args { if len(osArg) > tengo.MaxStringLen { - return nil, objects.ErrStringLimit + return nil, tengo.ErrStringLimit } - - arr.Value = append(arr.Value, &objects.String{Value: osArg}) + arr.Value = append(arr.Value, &tengo.String{Value: osArg}) } - return arr, nil } -func osFuncASFmRE(name string, fn func(string, os.FileMode) error) *objects.UserFunction { - return &objects.UserFunction{ +func osFuncASFmRE( + name string, + fn func(string, os.FileMode) error, +) *tengo.UserFunction { + return &tengo.UserFunction{ Name: name, - Value: func(args ...objects.Object) (objects.Object, error) { + Value: func(args ...tengo.Object) (tengo.Object, error) { if len(args) != 2 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - s1, ok := objects.ToString(args[0]) + s1, ok := tengo.ToString(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), } } - i2, ok := objects.ToInt64(args[1]) + i2, ok := tengo.ToInt64(args[1]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "second", Expected: "int(compatible)", Found: args[1].TypeName(), } } - return wrapError(fn(s1, os.FileMode(i2))), nil }, } } -func osLookupEnv(args ...objects.Object) (objects.Object, error) { +func osLookupEnv(args ...tengo.Object) (tengo.Object, error) { if len(args) != 1 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - s1, ok := objects.ToString(args[0]) + s1, ok := tengo.ToString(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), } } - res, ok := os.LookupEnv(s1) if !ok { - return objects.FalseValue, nil + return tengo.FalseValue, nil } - if len(res) > tengo.MaxStringLen { - return nil, objects.ErrStringLimit + return nil, tengo.ErrStringLimit } - - return &objects.String{Value: res}, nil + return &tengo.String{Value: res}, nil } -func osExpandEnv(args ...objects.Object) (objects.Object, error) { +func osExpandEnv(args ...tengo.Object) (tengo.Object, error) { if len(args) != 1 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - s1, ok := objects.ToString(args[0]) + s1, ok := tengo.ToString(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), } } - var vlen int var failed bool s := os.Expand(s1, func(k string) string { if failed { return "" } - v := os.Getenv(k) // this does not count the other texts that are not being replaced @@ -334,108 +421,96 @@ func osExpandEnv(args ...objects.Object) (objects.Object, error) { failed = true return "" } - return v }) - if failed || len(s) > tengo.MaxStringLen { - return nil, objects.ErrStringLimit + return nil, tengo.ErrStringLimit } - - return &objects.String{Value: s}, nil + return &tengo.String{Value: s}, nil } -func osExec(args ...objects.Object) (objects.Object, error) { +func osExec(args ...tengo.Object) (tengo.Object, error) { if len(args) == 0 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - name, ok := objects.ToString(args[0]) + name, ok := tengo.ToString(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), } } - var execArgs []string for idx, arg := range args[1:] { - execArg, ok := objects.ToString(arg) + execArg, ok := tengo.ToString(arg) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: fmt.Sprintf("args[%d]", idx), Expected: "string(compatible)", Found: args[1+idx].TypeName(), } } - execArgs = append(execArgs, execArg) } - return makeOSExecCommand(exec.Command(name, execArgs...)), nil } -func osFindProcess(args ...objects.Object) (objects.Object, error) { +func osFindProcess(args ...tengo.Object) (tengo.Object, error) { if len(args) != 1 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - i1, ok := objects.ToInt(args[0]) + i1, ok := tengo.ToInt(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "int(compatible)", Found: args[0].TypeName(), } } - proc, err := os.FindProcess(i1) if err != nil { return wrapError(err), nil } - return makeOSProcess(proc), nil } -func osStartProcess(args ...objects.Object) (objects.Object, error) { +func osStartProcess(args ...tengo.Object) (tengo.Object, error) { if len(args) != 4 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - name, ok := objects.ToString(args[0]) + name, ok := tengo.ToString(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), } } - var argv []string var err error switch arg1 := args[1].(type) { - case *objects.Array: + case *tengo.Array: argv, err = stringArray(arg1.Value, "second") if err != nil { return nil, err } - case *objects.ImmutableArray: + case *tengo.ImmutableArray: argv, err = stringArray(arg1.Value, "second") if err != nil { return nil, err } default: - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "second", Expected: "array", Found: arg1.TypeName(), } } - dir, ok := objects.ToString(args[2]) + dir, ok := tengo.ToString(args[2]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "third", Expected: "string(compatible)", Found: args[2].TypeName(), @@ -444,18 +519,18 @@ func osStartProcess(args ...objects.Object) (objects.Object, error) { var env []string switch arg3 := args[3].(type) { - case *objects.Array: + case *tengo.Array: env, err = stringArray(arg3.Value, "fourth") if err != nil { return nil, err } - case *objects.ImmutableArray: + case *tengo.ImmutableArray: env, err = stringArray(arg3.Value, "fourth") if err != nil { return nil, err } default: - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "fourth", Expected: "array", Found: arg3.TypeName(), @@ -469,24 +544,21 @@ func osStartProcess(args ...objects.Object) (objects.Object, error) { if err != nil { return wrapError(err), nil } - return makeOSProcess(proc), nil } -func stringArray(arr []objects.Object, argName string) ([]string, error) { +func stringArray(arr []tengo.Object, argName string) ([]string, error) { var sarr []string for idx, elem := range arr { - str, ok := elem.(*objects.String) + str, ok := elem.(*tengo.String) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: fmt.Sprintf("%s[%d]", argName, idx), Expected: "string", Found: elem.TypeName(), } } - sarr = append(sarr, str.Value) } - return sarr, nil } diff --git a/stdlib/os_exec.go b/stdlib/os_exec.go index 5274c36..eca4181 100644 --- a/stdlib/os_exec.go +++ b/stdlib/os_exec.go @@ -3,108 +3,114 @@ package stdlib import ( "os/exec" - "github.com/d5/tengo/objects" + "github.com/d5/tengo" ) -func makeOSExecCommand(cmd *exec.Cmd) *objects.ImmutableMap { - return &objects.ImmutableMap{ - Value: map[string]objects.Object{ +func makeOSExecCommand(cmd *exec.Cmd) *tengo.ImmutableMap { + return &tengo.ImmutableMap{ + Value: map[string]tengo.Object{ // combined_output() => bytes/error - "combined_output": &objects.UserFunction{Name: "combined_output", Value: FuncARYE(cmd.CombinedOutput)}, // + "combined_output": &tengo.UserFunction{ + Name: "combined_output", + Value: FuncARYE(cmd.CombinedOutput), + }, // output() => bytes/error - "output": &objects.UserFunction{Name: "output", Value: FuncARYE(cmd.Output)}, // + "output": &tengo.UserFunction{ + Name: "output", + Value: FuncARYE(cmd.Output), + }, // // run() => error - "run": &objects.UserFunction{Name: "run", Value: FuncARE(cmd.Run)}, // + "run": &tengo.UserFunction{ + Name: "run", + Value: FuncARE(cmd.Run), + }, // // start() => error - "start": &objects.UserFunction{Name: "start", Value: FuncARE(cmd.Start)}, // + "start": &tengo.UserFunction{ + Name: "start", + Value: FuncARE(cmd.Start), + }, // // wait() => error - "wait": &objects.UserFunction{Name: "wait", Value: FuncARE(cmd.Wait)}, // + "wait": &tengo.UserFunction{ + Name: "wait", + Value: FuncARE(cmd.Wait), + }, // // set_path(path string) - "set_path": &objects.UserFunction{ + "set_path": &tengo.UserFunction{ Name: "set_path", - Value: func(args ...objects.Object) (ret objects.Object, err error) { + Value: func(args ...tengo.Object) (tengo.Object, error) { if len(args) != 1 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - s1, ok := objects.ToString(args[0]) + s1, ok := tengo.ToString(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), } } - cmd.Path = s1 - - return objects.UndefinedValue, nil + return tengo.UndefinedValue, nil }, }, // set_dir(dir string) - "set_dir": &objects.UserFunction{ + "set_dir": &tengo.UserFunction{ Name: "set_dir", - Value: func(args ...objects.Object) (ret objects.Object, err error) { + Value: func(args ...tengo.Object) (tengo.Object, error) { if len(args) != 1 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - s1, ok := objects.ToString(args[0]) + s1, ok := tengo.ToString(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), } } - cmd.Dir = s1 - - return objects.UndefinedValue, nil + return tengo.UndefinedValue, nil }, }, // set_env(env array(string)) - "set_env": &objects.UserFunction{ + "set_env": &tengo.UserFunction{ Name: "set_env", - Value: func(args ...objects.Object) (objects.Object, error) { + Value: func(args ...tengo.Object) (tengo.Object, error) { if len(args) != 1 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } var env []string var err error switch arg0 := args[0].(type) { - case *objects.Array: + case *tengo.Array: env, err = stringArray(arg0.Value, "first") if err != nil { return nil, err } - case *objects.ImmutableArray: + case *tengo.ImmutableArray: env, err = stringArray(arg0.Value, "first") if err != nil { return nil, err } default: - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "array", Found: arg0.TypeName(), } } - cmd.Env = env - - return objects.UndefinedValue, nil + return tengo.UndefinedValue, nil }, }, // process() => imap(process) - "process": &objects.UserFunction{ + "process": &tengo.UserFunction{ Name: "process", - Value: func(args ...objects.Object) (ret objects.Object, err error) { + Value: func(args ...tengo.Object) (tengo.Object, error) { if len(args) != 0 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - return makeOSProcess(cmd.Process), nil }, }, diff --git a/stdlib/os_file.go b/stdlib/os_file.go index bd3d6e2..43293f2 100644 --- a/stdlib/os_file.go +++ b/stdlib/os_file.go @@ -3,92 +3,113 @@ package stdlib import ( "os" - "github.com/d5/tengo/objects" + "github.com/d5/tengo" ) -func makeOSFile(file *os.File) *objects.ImmutableMap { - return &objects.ImmutableMap{ - Value: map[string]objects.Object{ +func makeOSFile(file *os.File) *tengo.ImmutableMap { + return &tengo.ImmutableMap{ + Value: map[string]tengo.Object{ // chdir() => true/error - "chdir": &objects.UserFunction{Name: "chdir", Value: FuncARE(file.Chdir)}, // + "chdir": &tengo.UserFunction{ + Name: "chdir", + Value: FuncARE(file.Chdir), + }, // // chown(uid int, gid int) => true/error - "chown": &objects.UserFunction{Name: "chown", Value: FuncAIIRE(file.Chown)}, // + "chown": &tengo.UserFunction{ + Name: "chown", + Value: FuncAIIRE(file.Chown), + }, // // close() => error - "close": &objects.UserFunction{Name: "close", Value: FuncARE(file.Close)}, // + "close": &tengo.UserFunction{ + Name: "close", + Value: FuncARE(file.Close), + }, // // name() => string - "name": &objects.UserFunction{Name: "name", Value: FuncARS(file.Name)}, // + "name": &tengo.UserFunction{ + Name: "name", + Value: FuncARS(file.Name), + }, // // readdirnames(n int) => array(string)/error - "readdirnames": &objects.UserFunction{Name: "readdirnames", Value: FuncAIRSsE(file.Readdirnames)}, // + "readdirnames": &tengo.UserFunction{ + Name: "readdirnames", + Value: FuncAIRSsE(file.Readdirnames), + }, // // sync() => error - "sync": &objects.UserFunction{Name: "sync", Value: FuncARE(file.Sync)}, // + "sync": &tengo.UserFunction{ + Name: "sync", + Value: FuncARE(file.Sync), + }, // // write(bytes) => int/error - "write": &objects.UserFunction{Name: "write", Value: FuncAYRIE(file.Write)}, // + "write": &tengo.UserFunction{ + Name: "write", + Value: FuncAYRIE(file.Write), + }, // // write(string) => int/error - "write_string": &objects.UserFunction{Name: "write_string", Value: FuncASRIE(file.WriteString)}, // + "write_string": &tengo.UserFunction{ + Name: "write_string", + Value: FuncASRIE(file.WriteString), + }, // // read(bytes) => int/error - "read": &objects.UserFunction{Name: "read", Value: FuncAYRIE(file.Read)}, // + "read": &tengo.UserFunction{ + Name: "read", + Value: FuncAYRIE(file.Read), + }, // // chmod(mode int) => error - "chmod": &objects.UserFunction{ + "chmod": &tengo.UserFunction{ Name: "chmod", - Value: func(args ...objects.Object) (ret objects.Object, err error) { + Value: func(args ...tengo.Object) (tengo.Object, error) { if len(args) != 1 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - i1, ok := objects.ToInt64(args[0]) + i1, ok := tengo.ToInt64(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "int(compatible)", Found: args[0].TypeName(), } } - return wrapError(file.Chmod(os.FileMode(i1))), nil }, }, // seek(offset int, whence int) => int/error - "seek": &objects.UserFunction{ + "seek": &tengo.UserFunction{ Name: "seek", - Value: func(args ...objects.Object) (ret objects.Object, err error) { + Value: func(args ...tengo.Object) (tengo.Object, error) { if len(args) != 2 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - i1, ok := objects.ToInt64(args[0]) + i1, ok := tengo.ToInt64(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "int(compatible)", Found: args[0].TypeName(), } } - i2, ok := objects.ToInt(args[1]) + i2, ok := tengo.ToInt(args[1]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "second", Expected: "int(compatible)", Found: args[1].TypeName(), } } - res, err := file.Seek(i1, i2) if err != nil { return wrapError(err), nil } - - return &objects.Int{Value: res}, nil + return &tengo.Int{Value: res}, nil }, }, // stat() => imap(fileinfo)/error - "stat": &objects.UserFunction{ + "stat": &tengo.UserFunction{ Name: "stat", - Value: func(args ...objects.Object) (ret objects.Object, err error) { + Value: func(args ...tengo.Object) (tengo.Object, error) { if len(args) != 0 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - return osStat(&objects.String{Value: file.Name()}) + return osStat(&tengo.String{Value: file.Name()}) }, }, }, diff --git a/stdlib/os_process.go b/stdlib/os_process.go index 801ccde..621e569 100644 --- a/stdlib/os_process.go +++ b/stdlib/os_process.go @@ -4,56 +4,70 @@ import ( "os" "syscall" - "github.com/d5/tengo/objects" + "github.com/d5/tengo" ) -func makeOSProcessState(state *os.ProcessState) *objects.ImmutableMap { - return &objects.ImmutableMap{ - Value: map[string]objects.Object{ - "exited": &objects.UserFunction{Name: "exited", Value: FuncARB(state.Exited)}, // - "pid": &objects.UserFunction{Name: "pid", Value: FuncARI(state.Pid)}, // - "string": &objects.UserFunction{Name: "string", Value: FuncARS(state.String)}, // - "success": &objects.UserFunction{Name: "success", Value: FuncARB(state.Success)}, // +func makeOSProcessState(state *os.ProcessState) *tengo.ImmutableMap { + return &tengo.ImmutableMap{ + Value: map[string]tengo.Object{ + "exited": &tengo.UserFunction{ + Name: "exited", + Value: FuncARB(state.Exited), + }, + "pid": &tengo.UserFunction{ + Name: "pid", + Value: FuncARI(state.Pid), + }, + "string": &tengo.UserFunction{ + Name: "string", + Value: FuncARS(state.String), + }, + "success": &tengo.UserFunction{ + Name: "success", + Value: FuncARB(state.Success), + }, }, } } -func makeOSProcess(proc *os.Process) *objects.ImmutableMap { - return &objects.ImmutableMap{ - Value: map[string]objects.Object{ - "kill": &objects.UserFunction{Name: "kill", Value: FuncARE(proc.Kill)}, // - "release": &objects.UserFunction{Name: "release", Value: FuncARE(proc.Release)}, // - "signal": &objects.UserFunction{ +func makeOSProcess(proc *os.Process) *tengo.ImmutableMap { + return &tengo.ImmutableMap{ + Value: map[string]tengo.Object{ + "kill": &tengo.UserFunction{ + Name: "kill", + Value: FuncARE(proc.Kill), + }, + "release": &tengo.UserFunction{ + Name: "release", + Value: FuncARE(proc.Release), + }, + "signal": &tengo.UserFunction{ Name: "signal", - Value: func(args ...objects.Object) (ret objects.Object, err error) { + Value: func(args ...tengo.Object) (tengo.Object, error) { if len(args) != 1 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - i1, ok := objects.ToInt64(args[0]) + i1, ok := tengo.ToInt64(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "int(compatible)", Found: args[0].TypeName(), } } - return wrapError(proc.Signal(syscall.Signal(i1))), nil }, }, - "wait": &objects.UserFunction{ + "wait": &tengo.UserFunction{ Name: "wait", - Value: func(args ...objects.Object) (ret objects.Object, err error) { + Value: func(args ...tengo.Object) (tengo.Object, error) { if len(args) != 0 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - state, err := proc.Wait() if err != nil { return wrapError(err), nil } - return makeOSProcessState(state), nil }, }, diff --git a/stdlib/os_test.go b/stdlib/os_test.go index 012f584..ea52d8b 100644 --- a/stdlib/os_test.go +++ b/stdlib/os_test.go @@ -6,25 +6,21 @@ import ( "testing" "github.com/d5/tengo" - "github.com/d5/tengo/assert" - "github.com/d5/tengo/objects" + "github.com/d5/tengo/internal/require" ) func TestReadFile(t *testing.T) { content := []byte("the quick brown fox jumps over the lazy dog") tf, err := ioutil.TempFile("", "test") - if !assert.NoError(t, err) { - return - } + require.NoError(t, err) defer func() { _ = os.Remove(tf.Name()) }() _, err = tf.Write(content) - if !assert.NoError(t, err) { - return - } + require.NoError(t, err) _ = tf.Close() - module(t, "os").call("read_file", tf.Name()).expect(&objects.Bytes{Value: content}) + module(t, "os").call("read_file", tf.Name()). + expect(&tengo.Bytes{Value: content}) } func TestReadFileArgs(t *testing.T) { @@ -37,15 +33,11 @@ func TestFileStatArgs(t *testing.T) { func TestFileStatFile(t *testing.T) { content := []byte("the quick brown fox jumps over the lazy dog") tf, err := ioutil.TempFile("", "test") - if !assert.NoError(t, err) { - return - } + require.NoError(t, err) defer func() { _ = os.Remove(tf.Name()) }() _, err = tf.Write(content) - if !assert.NoError(t, err) { - return - } + require.NoError(t, err) _ = tf.Close() stat, err := os.Stat(tf.Name()) @@ -54,36 +46,32 @@ func TestFileStatFile(t *testing.T) { return } - module(t, "os").call("stat", tf.Name()).expect(&objects.ImmutableMap{ - Value: map[string]objects.Object{ - "name": &objects.String{Value: stat.Name()}, - "mtime": &objects.Time{Value: stat.ModTime()}, - "size": &objects.Int{Value: stat.Size()}, - "mode": &objects.Int{Value: int64(stat.Mode())}, - "directory": objects.FalseValue, + module(t, "os").call("stat", tf.Name()).expect(&tengo.ImmutableMap{ + Value: map[string]tengo.Object{ + "name": &tengo.String{Value: stat.Name()}, + "mtime": &tengo.Time{Value: stat.ModTime()}, + "size": &tengo.Int{Value: stat.Size()}, + "mode": &tengo.Int{Value: int64(stat.Mode())}, + "directory": tengo.FalseValue, }, }) } func TestFileStatDir(t *testing.T) { td, err := ioutil.TempDir("", "test") - if !assert.NoError(t, err) { - return - } + require.NoError(t, err) defer func() { _ = os.RemoveAll(td) }() stat, err := os.Stat(td) - if !assert.NoError(t, err) { - return - } + require.NoError(t, err) - module(t, "os").call("stat", td).expect(&objects.ImmutableMap{ - Value: map[string]objects.Object{ - "name": &objects.String{Value: stat.Name()}, - "mtime": &objects.Time{Value: stat.ModTime()}, - "size": &objects.Int{Value: stat.Size()}, - "mode": &objects.Int{Value: int64(stat.Mode())}, - "directory": objects.TrueValue, + module(t, "os").call("stat", td).expect(&tengo.ImmutableMap{ + Value: map[string]tengo.Object{ + "name": &tengo.String{Value: stat.Name()}, + "mtime": &tengo.Time{Value: stat.ModTime()}, + "size": &tengo.Int{Value: stat.Size()}, + "mode": &tengo.Int{Value: int64(stat.Mode())}, + "directory": tengo.TrueValue, }, }) } @@ -109,7 +97,8 @@ func TestOSExpandEnv(t *testing.T) { module(t, "os").call("expand_env", "$TENGO$TENGO").expect("123456123456") _ = os.Setenv("TENGO", "123456") - module(t, "os").call("expand_env", "${TENGO}${TENGO}").expect("123456123456") + module(t, "os").call("expand_env", "${TENGO}${TENGO}"). + expect("123456123456") _ = os.Setenv("TENGO", "123456") module(t, "os").call("expand_env", "$TENGO $TENGO").expectError() diff --git a/stdlib/rand.go b/stdlib/rand.go index 6efe1de..311b5d9 100644 --- a/stdlib/rand.go +++ b/stdlib/rand.go @@ -3,98 +3,134 @@ package stdlib import ( "math/rand" - "github.com/d5/tengo/objects" + "github.com/d5/tengo" ) -var randModule = map[string]objects.Object{ - "int": &objects.UserFunction{Name: "int", Value: FuncARI64(rand.Int63)}, - "float": &objects.UserFunction{Name: "float", Value: FuncARF(rand.Float64)}, - "intn": &objects.UserFunction{Name: "intn", Value: FuncAI64RI64(rand.Int63n)}, - "exp_float": &objects.UserFunction{Name: "exp_float", Value: FuncARF(rand.ExpFloat64)}, - "norm_float": &objects.UserFunction{Name: "norm_float", Value: FuncARF(rand.NormFloat64)}, - "perm": &objects.UserFunction{Name: "perm", Value: FuncAIRIs(rand.Perm)}, - "seed": &objects.UserFunction{Name: "seed", Value: FuncAI64R(rand.Seed)}, - "read": &objects.UserFunction{ +var randModule = map[string]tengo.Object{ + "int": &tengo.UserFunction{ + Name: "int", + Value: FuncARI64(rand.Int63), + }, + "float": &tengo.UserFunction{ + Name: "float", + Value: FuncARF(rand.Float64), + }, + "intn": &tengo.UserFunction{ + Name: "intn", + Value: FuncAI64RI64(rand.Int63n), + }, + "exp_float": &tengo.UserFunction{ + Name: "exp_float", + Value: FuncARF(rand.ExpFloat64), + }, + "norm_float": &tengo.UserFunction{ + Name: "norm_float", + Value: FuncARF(rand.NormFloat64), + }, + "perm": &tengo.UserFunction{ + Name: "perm", + Value: FuncAIRIs(rand.Perm), + }, + "seed": &tengo.UserFunction{ + Name: "seed", + Value: FuncAI64R(rand.Seed), + }, + "read": &tengo.UserFunction{ Name: "read", - Value: func(args ...objects.Object) (ret objects.Object, err error) { + Value: func(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 1 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - y1, ok := args[0].(*objects.Bytes) + y1, ok := args[0].(*tengo.Bytes) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "bytes", Found: args[0].TypeName(), } } - res, err := rand.Read(y1.Value) if err != nil { ret = wrapError(err) return } - - return &objects.Int{Value: int64(res)}, nil + return &tengo.Int{Value: int64(res)}, nil }, }, - "rand": &objects.UserFunction{ + "rand": &tengo.UserFunction{ Name: "rand", - Value: func(args ...objects.Object) (ret objects.Object, err error) { + Value: func(args ...tengo.Object) (tengo.Object, error) { if len(args) != 1 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - i1, ok := objects.ToInt64(args[0]) + i1, ok := tengo.ToInt64(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "int(compatible)", Found: args[0].TypeName(), } } - src := rand.NewSource(i1) - return randRand(rand.New(src)), nil }, }, } -func randRand(r *rand.Rand) *objects.ImmutableMap { - return &objects.ImmutableMap{ - Value: map[string]objects.Object{ - "int": &objects.UserFunction{Name: "int", Value: FuncARI64(r.Int63)}, - "float": &objects.UserFunction{Name: "float", Value: FuncARF(r.Float64)}, - "intn": &objects.UserFunction{Name: "intn", Value: FuncAI64RI64(r.Int63n)}, - "exp_float": &objects.UserFunction{Name: "exp_float", Value: FuncARF(r.ExpFloat64)}, - "norm_float": &objects.UserFunction{Name: "norm_float", Value: FuncARF(r.NormFloat64)}, - "perm": &objects.UserFunction{Name: "perm", Value: FuncAIRIs(r.Perm)}, - "seed": &objects.UserFunction{Name: "seed", Value: FuncAI64R(r.Seed)}, - "read": &objects.UserFunction{ +func randRand(r *rand.Rand) *tengo.ImmutableMap { + return &tengo.ImmutableMap{ + Value: map[string]tengo.Object{ + "int": &tengo.UserFunction{ + Name: "int", + Value: FuncARI64(r.Int63), + }, + "float": &tengo.UserFunction{ + Name: "float", + Value: FuncARF(r.Float64), + }, + "intn": &tengo.UserFunction{ + Name: "intn", + Value: FuncAI64RI64(r.Int63n), + }, + "exp_float": &tengo.UserFunction{ + Name: "exp_float", + Value: FuncARF(r.ExpFloat64), + }, + "norm_float": &tengo.UserFunction{ + Name: "norm_float", + Value: FuncARF(r.NormFloat64), + }, + "perm": &tengo.UserFunction{ + Name: "perm", + Value: FuncAIRIs(r.Perm), + }, + "seed": &tengo.UserFunction{ + Name: "seed", + Value: FuncAI64R(r.Seed), + }, + "read": &tengo.UserFunction{ Name: "read", - Value: func(args ...objects.Object) (ret objects.Object, err error) { + Value: func(args ...tengo.Object) ( + ret tengo.Object, + err error, + ) { if len(args) != 1 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - - y1, ok := args[0].(*objects.Bytes) + y1, ok := args[0].(*tengo.Bytes) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "bytes", Found: args[0].TypeName(), } } - res, err := r.Read(y1.Value) if err != nil { ret = wrapError(err) return } - - return &objects.Int{Value: int64(res)}, nil + return &tengo.Int{Value: int64(res)}, nil }, }, }, diff --git a/stdlib/rand_test.go b/stdlib/rand_test.go index ad08711..ff4b883 100644 --- a/stdlib/rand_test.go +++ b/stdlib/rand_test.go @@ -4,15 +4,15 @@ import ( "math/rand" "testing" - "github.com/d5/tengo/assert" - "github.com/d5/tengo/objects" + "github.com/d5/tengo" + "github.com/d5/tengo/internal/require" ) func TestRand(t *testing.T) { var seed int64 = 1234 r := rand.New(rand.NewSource(seed)) - module(t, "rand").call("seed", seed).expect(objects.UndefinedValue) + module(t, "rand").call("seed", seed).expect(tengo.UndefinedValue) module(t, "rand").call("int").expect(r.Int63()) module(t, "rand").call("float").expect(r.Float64()) module(t, "rand").call("intn", 111).expect(r.Int63n(111)) @@ -21,15 +21,15 @@ func TestRand(t *testing.T) { module(t, "rand").call("perm", 10).expect(r.Perm(10)) buf1 := make([]byte, 10) - buf2 := &objects.Bytes{Value: make([]byte, 10)} + buf2 := &tengo.Bytes{Value: make([]byte, 10)} n, _ := r.Read(buf1) module(t, "rand").call("read", buf2).expect(n) - assert.Equal(t, buf1, buf2.Value) + require.Equal(t, buf1, buf2.Value) seed = 9191 r = rand.New(rand.NewSource(seed)) randObj := module(t, "rand").call("rand", seed) - randObj.call("seed", seed).expect(objects.UndefinedValue) + randObj.call("seed", seed).expect(tengo.UndefinedValue) randObj.call("int").expect(r.Int63()) randObj.call("float").expect(r.Float64()) randObj.call("intn", 111).expect(r.Int63n(111)) @@ -38,8 +38,8 @@ func TestRand(t *testing.T) { randObj.call("perm", 10).expect(r.Perm(10)) buf1 = make([]byte, 12) - buf2 = &objects.Bytes{Value: make([]byte, 12)} + buf2 = &tengo.Bytes{Value: make([]byte, 12)} n, _ = r.Read(buf1) randObj.call("read", buf2).expect(n) - assert.Equal(t, buf1, buf2.Value) + require.Equal(t, buf1, buf2.Value) } diff --git a/stdlib/stdlib.go b/stdlib/stdlib.go index aad220e..5cafcd0 100644 --- a/stdlib/stdlib.go +++ b/stdlib/stdlib.go @@ -2,7 +2,9 @@ package stdlib //go:generate go run gensrcmods.go -import "github.com/d5/tengo/objects" +import ( + "github.com/d5/tengo" +) // AllModuleNames returns a list of all default module names. func AllModuleNames() []string { @@ -18,9 +20,8 @@ func AllModuleNames() []string { // GetModuleMap returns the module map that includes all modules // for the given module names. -func GetModuleMap(names ...string) *objects.ModuleMap { - modules := objects.NewModuleMap() - +func GetModuleMap(names ...string) *tengo.ModuleMap { + modules := tengo.NewModuleMap() for _, name := range names { if mod := BuiltinModules[name]; mod != nil { modules.AddBuiltinModule(name, mod) @@ -29,6 +30,5 @@ func GetModuleMap(names ...string) *objects.ModuleMap { modules.AddSourceModule(name, []byte(mod)) } } - return modules } diff --git a/stdlib/stdlib_test.go b/stdlib/stdlib_test.go index 0ea56aa..03f6658 100644 --- a/stdlib/stdlib_test.go +++ b/stdlib/stdlib_test.go @@ -5,9 +5,8 @@ import ( "testing" "time" - "github.com/d5/tengo/assert" - "github.com/d5/tengo/objects" - "github.com/d5/tengo/script" + "github.com/d5/tengo" + "github.com/d5/tengo/internal/require" "github.com/d5/tengo/stdlib" ) @@ -18,9 +17,9 @@ type IMAP map[string]interface{} func TestAllModuleNames(t *testing.T) { names := stdlib.AllModuleNames() - if !assert.Equal(t, len(stdlib.BuiltinModules)+len(stdlib.SourceModules), len(names)) { - return - } + require.Equal(t, + len(stdlib.BuiltinModules)+len(stdlib.SourceModules), + len(names)) } func TestModulesRun(t *testing.T) { @@ -75,24 +74,24 @@ if !is_error(cmd) { func TestGetModules(t *testing.T) { mods := stdlib.GetModuleMap() - assert.Equal(t, 0, mods.Len()) + require.Equal(t, 0, mods.Len()) mods = stdlib.GetModuleMap("os") - assert.Equal(t, 1, mods.Len()) - assert.NotNil(t, mods.Get("os")) + require.Equal(t, 1, mods.Len()) + require.NotNil(t, mods.Get("os")) mods = stdlib.GetModuleMap("os", "rand") - assert.Equal(t, 2, mods.Len()) - assert.NotNil(t, mods.Get("os")) - assert.NotNil(t, mods.Get("rand")) + require.Equal(t, 2, mods.Len()) + require.NotNil(t, mods.Get("os")) + require.NotNil(t, mods.Get("rand")) mods = stdlib.GetModuleMap("text", "text") - assert.Equal(t, 1, mods.Len()) - assert.NotNil(t, mods.Get("text")) + require.Equal(t, 1, mods.Len()) + require.NotNil(t, mods.Get("text")) mods = stdlib.GetModuleMap("nonexisting", "text") - assert.Equal(t, 1, mods.Len()) - assert.NotNil(t, mods.Get("text")) + require.Equal(t, 1, mods.Len()) + require.NotNil(t, mods.Get("text")) } type callres struct { @@ -106,35 +105,37 @@ func (c callres) call(funcName string, args ...interface{}) callres { return c } - var oargs []objects.Object + var oargs []tengo.Object for _, v := range args { oargs = append(oargs, object(v)) } switch o := c.o.(type) { - case *objects.BuiltinModule: + case *tengo.BuiltinModule: m, ok := o.Attrs[funcName] if !ok { - return callres{t: c.t, e: fmt.Errorf("function not found: %s", funcName)} + return callres{t: c.t, e: fmt.Errorf( + "function not found: %s", funcName)} } - f, ok := m.(*objects.UserFunction) + f, ok := m.(*tengo.UserFunction) if !ok { - return callres{t: c.t, e: fmt.Errorf("non-callable: %s", funcName)} + return callres{t: c.t, e: fmt.Errorf( + "non-callable: %s", funcName)} } res, err := f.Value(oargs...) return callres{t: c.t, o: res, e: err} - case *objects.UserFunction: + case *tengo.UserFunction: res, err := o.Value(oargs...) return callres{t: c.t, o: res, e: err} - case *objects.ImmutableMap: + case *tengo.ImmutableMap: m, ok := o.Value[funcName] if !ok { return callres{t: c.t, e: fmt.Errorf("function not found: %s", funcName)} } - f, ok := m.(*objects.UserFunction) + f, ok := m.(*tengo.UserFunction) if !ok { return callres{t: c.t, e: fmt.Errorf("non-callable: %s", funcName)} } @@ -146,13 +147,13 @@ func (c callres) call(funcName string, args ...interface{}) callres { } } -func (c callres) expect(expected interface{}, msgAndArgs ...interface{}) bool { - return assert.NoError(c.t, c.e, msgAndArgs...) && - assert.Equal(c.t, object(expected), c.o, msgAndArgs...) +func (c callres) expect(expected interface{}, msgAndArgs ...interface{}) { + require.NoError(c.t, c.e, msgAndArgs...) + require.Equal(c.t, object(expected), c.o, msgAndArgs...) } -func (c callres) expectError() bool { - return assert.Error(c.t, c.e) +func (c callres) expectError() { + require.Error(c.t, c.e) } func module(t *testing.T, moduleName string) callres { @@ -164,81 +165,78 @@ func module(t *testing.T, moduleName string) callres { return callres{t: t, o: mod} } -func object(v interface{}) objects.Object { +func object(v interface{}) tengo.Object { switch v := v.(type) { - case objects.Object: + case tengo.Object: return v case string: - return &objects.String{Value: v} + return &tengo.String{Value: v} case int64: - return &objects.Int{Value: v} + return &tengo.Int{Value: v} case int: // for convenience - return &objects.Int{Value: int64(v)} + return &tengo.Int{Value: int64(v)} case bool: if v { - return objects.TrueValue + return tengo.TrueValue } - return objects.FalseValue + return tengo.FalseValue case rune: - return &objects.Char{Value: v} + return &tengo.Char{Value: v} case byte: // for convenience - return &objects.Char{Value: rune(v)} + return &tengo.Char{Value: rune(v)} case float64: - return &objects.Float{Value: v} + return &tengo.Float{Value: v} case []byte: - return &objects.Bytes{Value: v} + return &tengo.Bytes{Value: v} case MAP: - objs := make(map[string]objects.Object) + objs := make(map[string]tengo.Object) for k, v := range v { objs[k] = object(v) } - return &objects.Map{Value: objs} + return &tengo.Map{Value: objs} case ARR: - var objs []objects.Object + var objs []tengo.Object for _, e := range v { objs = append(objs, object(e)) } - return &objects.Array{Value: objs} + return &tengo.Array{Value: objs} case IMAP: - objs := make(map[string]objects.Object) + objs := make(map[string]tengo.Object) for k, v := range v { objs[k] = object(v) } - return &objects.ImmutableMap{Value: objs} + return &tengo.ImmutableMap{Value: objs} case IARR: - var objs []objects.Object + var objs []tengo.Object for _, e := range v { objs = append(objs, object(e)) } - return &objects.ImmutableArray{Value: objs} + return &tengo.ImmutableArray{Value: objs} case time.Time: - return &objects.Time{Value: v} + return &tengo.Time{Value: v} case []int: - var objs []objects.Object + var objs []tengo.Object for _, e := range v { - objs = append(objs, &objects.Int{Value: int64(e)}) + objs = append(objs, &tengo.Int{Value: int64(e)}) } - return &objects.Array{Value: objs} + return &tengo.Array{Value: objs} } panic(fmt.Errorf("unknown type: %T", v)) } func expect(t *testing.T, input string, expected interface{}) { - s := script.New([]byte(input)) + s := tengo.NewScript([]byte(input)) s.SetImports(stdlib.GetModuleMap(stdlib.AllModuleNames()...)) c, err := s.Run() - assert.NoError(t, err) - assert.NotNil(t, c) + require.NoError(t, err) + require.NotNil(t, c) v := c.Get("out") - if !assert.NotNil(t, v) { - return - } - - assert.Equal(t, expected, v.Value()) + require.NotNil(t, v) + require.Equal(t, expected, v.Value()) } diff --git a/stdlib/text.go b/stdlib/text.go index 4b5729e..99edf37 100644 --- a/stdlib/text.go +++ b/stdlib/text.go @@ -8,68 +8,208 @@ import ( "unicode/utf8" "github.com/d5/tengo" - "github.com/d5/tengo/objects" ) -var textModule = map[string]objects.Object{ - "re_match": &objects.UserFunction{Name: "re_match", Value: textREMatch}, // re_match(pattern, text) => bool/error - "re_find": &objects.UserFunction{Name: "re_find", Value: textREFind}, // re_find(pattern, text, count) => [[{text:,begin:,end:}]]/undefined - "re_replace": &objects.UserFunction{Name: "re_replace", Value: textREReplace}, // re_replace(pattern, text, repl) => string/error - "re_split": &objects.UserFunction{Name: "re_split", Value: textRESplit}, // re_split(pattern, text, count) => [string]/error - "re_compile": &objects.UserFunction{Name: "re_compile", Value: textRECompile}, // re_compile(pattern) => Regexp/error - "compare": &objects.UserFunction{Name: "compare", Value: FuncASSRI(strings.Compare)}, // compare(a, b) => int - "contains": &objects.UserFunction{Name: "contains", Value: FuncASSRB(strings.Contains)}, // contains(s, substr) => bool - "contains_any": &objects.UserFunction{Name: "contains_any", Value: FuncASSRB(strings.ContainsAny)}, // contains_any(s, chars) => bool - "count": &objects.UserFunction{Name: "count", Value: FuncASSRI(strings.Count)}, // count(s, substr) => int - "equal_fold": &objects.UserFunction{Name: "equal_fold", Value: FuncASSRB(strings.EqualFold)}, // "equal_fold(s, t) => bool - "fields": &objects.UserFunction{Name: "fields", Value: FuncASRSs(strings.Fields)}, // fields(s) => [string] - "has_prefix": &objects.UserFunction{Name: "has_prefix", Value: FuncASSRB(strings.HasPrefix)}, // has_prefix(s, prefix) => bool - "has_suffix": &objects.UserFunction{Name: "has_suffix", Value: FuncASSRB(strings.HasSuffix)}, // has_suffix(s, suffix) => bool - "index": &objects.UserFunction{Name: "index", Value: FuncASSRI(strings.Index)}, // index(s, substr) => int - "index_any": &objects.UserFunction{Name: "index_any", Value: FuncASSRI(strings.IndexAny)}, // index_any(s, chars) => int - "join": &objects.UserFunction{Name: "join", Value: textJoin}, // join(arr, sep) => string - "last_index": &objects.UserFunction{Name: "last_index", Value: FuncASSRI(strings.LastIndex)}, // last_index(s, substr) => int - "last_index_any": &objects.UserFunction{Name: "last_index_any", Value: FuncASSRI(strings.LastIndexAny)}, // last_index_any(s, chars) => int - "repeat": &objects.UserFunction{Name: "repeat", Value: textRepeat}, // repeat(s, count) => string - "replace": &objects.UserFunction{Name: "replace", Value: textReplace}, // replace(s, old, new, n) => string - "substr": &objects.UserFunction{Name: "substr", Value: textSubstring}, // substr(s, lower, upper) => string - "split": &objects.UserFunction{Name: "split", Value: FuncASSRSs(strings.Split)}, // split(s, sep) => [string] - "split_after": &objects.UserFunction{Name: "split_after", Value: FuncASSRSs(strings.SplitAfter)}, // split_after(s, sep) => [string] - "split_after_n": &objects.UserFunction{Name: "split_after_n", Value: FuncASSIRSs(strings.SplitAfterN)}, // split_after_n(s, sep, n) => [string] - "split_n": &objects.UserFunction{Name: "split_n", Value: FuncASSIRSs(strings.SplitN)}, // split_n(s, sep, n) => [string] - "title": &objects.UserFunction{Name: "title", Value: FuncASRS(strings.Title)}, // title(s) => string - "to_lower": &objects.UserFunction{Name: "to_lower", Value: FuncASRS(strings.ToLower)}, // to_lower(s) => string - "to_title": &objects.UserFunction{Name: "to_title", Value: FuncASRS(strings.ToTitle)}, // to_title(s) => string - "to_upper": &objects.UserFunction{Name: "to_upper", Value: FuncASRS(strings.ToUpper)}, // to_upper(s) => string - "pad_left": &objects.UserFunction{Name: "pad_left", Value: textPadLeft}, // pad_left(s, pad_len, pad_with) => string - "pad_right": &objects.UserFunction{Name: "pad_right", Value: textPadRight}, // pad_right(s, pad_len, pad_with) => string - "trim": &objects.UserFunction{Name: "trim", Value: FuncASSRS(strings.Trim)}, // trim(s, cutset) => string - "trim_left": &objects.UserFunction{Name: "trim_left", Value: FuncASSRS(strings.TrimLeft)}, // trim_left(s, cutset) => string - "trim_prefix": &objects.UserFunction{Name: "trim_prefix", Value: FuncASSRS(strings.TrimPrefix)}, // trim_prefix(s, prefix) => string - "trim_right": &objects.UserFunction{Name: "trim_right", Value: FuncASSRS(strings.TrimRight)}, // trim_right(s, cutset) => string - "trim_space": &objects.UserFunction{Name: "trim_space", Value: FuncASRS(strings.TrimSpace)}, // trim_space(s) => string - "trim_suffix": &objects.UserFunction{Name: "trim_suffix", Value: FuncASSRS(strings.TrimSuffix)}, // trim_suffix(s, suffix) => string - "atoi": &objects.UserFunction{Name: "atoi", Value: FuncASRIE(strconv.Atoi)}, // atoi(str) => int/error - "format_bool": &objects.UserFunction{Name: "format_bool", Value: textFormatBool}, // format_bool(b) => string - "format_float": &objects.UserFunction{Name: "format_float", Value: textFormatFloat}, // format_float(f, fmt, prec, bits) => string - "format_int": &objects.UserFunction{Name: "format_int", Value: textFormatInt}, // format_int(i, base) => string - "itoa": &objects.UserFunction{Name: "itoa", Value: FuncAIRS(strconv.Itoa)}, // itoa(i) => string - "parse_bool": &objects.UserFunction{Name: "parse_bool", Value: textParseBool}, // parse_bool(str) => bool/error - "parse_float": &objects.UserFunction{Name: "parse_float", Value: textParseFloat}, // parse_float(str, bits) => float/error - "parse_int": &objects.UserFunction{Name: "parse_int", Value: textParseInt}, // parse_int(str, base, bits) => int/error - "quote": &objects.UserFunction{Name: "quote", Value: FuncASRS(strconv.Quote)}, // quote(str) => string - "unquote": &objects.UserFunction{Name: "unquote", Value: FuncASRSE(strconv.Unquote)}, // unquote(str) => string/error +var textModule = map[string]tengo.Object{ + "re_match": &tengo.UserFunction{ + Name: "re_match", + Value: textREMatch, + }, // re_match(pattern, text) => bool/error + "re_find": &tengo.UserFunction{ + Name: "re_find", + Value: textREFind, + }, // re_find(pattern, text, count) => [[{text:,begin:,end:}]]/undefined + "re_replace": &tengo.UserFunction{ + Name: "re_replace", + Value: textREReplace, + }, // re_replace(pattern, text, repl) => string/error + "re_split": &tengo.UserFunction{ + Name: "re_split", + Value: textRESplit, + }, // re_split(pattern, text, count) => [string]/error + "re_compile": &tengo.UserFunction{ + Name: "re_compile", + Value: textRECompile, + }, // re_compile(pattern) => Regexp/error + "compare": &tengo.UserFunction{ + Name: "compare", + Value: FuncASSRI(strings.Compare), + }, // compare(a, b) => int + "contains": &tengo.UserFunction{ + Name: "contains", + Value: FuncASSRB(strings.Contains), + }, // contains(s, substr) => bool + "contains_any": &tengo.UserFunction{ + Name: "contains_any", + Value: FuncASSRB(strings.ContainsAny), + }, // contains_any(s, chars) => bool + "count": &tengo.UserFunction{ + Name: "count", + Value: FuncASSRI(strings.Count), + }, // count(s, substr) => int + "equal_fold": &tengo.UserFunction{ + Name: "equal_fold", + Value: FuncASSRB(strings.EqualFold), + }, // "equal_fold(s, t) => bool + "fields": &tengo.UserFunction{ + Name: "fields", + Value: FuncASRSs(strings.Fields), + }, // fields(s) => [string] + "has_prefix": &tengo.UserFunction{ + Name: "has_prefix", + Value: FuncASSRB(strings.HasPrefix), + }, // has_prefix(s, prefix) => bool + "has_suffix": &tengo.UserFunction{ + Name: "has_suffix", + Value: FuncASSRB(strings.HasSuffix), + }, // has_suffix(s, suffix) => bool + "index": &tengo.UserFunction{ + Name: "index", + Value: FuncASSRI(strings.Index), + }, // index(s, substr) => int + "index_any": &tengo.UserFunction{ + Name: "index_any", + Value: FuncASSRI(strings.IndexAny), + }, // index_any(s, chars) => int + "join": &tengo.UserFunction{ + Name: "join", + Value: textJoin, + }, // join(arr, sep) => string + "last_index": &tengo.UserFunction{ + Name: "last_index", + Value: FuncASSRI(strings.LastIndex), + }, // last_index(s, substr) => int + "last_index_any": &tengo.UserFunction{ + Name: "last_index_any", + Value: FuncASSRI(strings.LastIndexAny), + }, // last_index_any(s, chars) => int + "repeat": &tengo.UserFunction{ + Name: "repeat", + Value: textRepeat, + }, // repeat(s, count) => string + "replace": &tengo.UserFunction{ + Name: "replace", + Value: textReplace, + }, // replace(s, old, new, n) => string + "substr": &tengo.UserFunction{ + Name: "substr", + Value: textSubstring, + }, // substr(s, lower, upper) => string + "split": &tengo.UserFunction{ + Name: "split", + Value: FuncASSRSs(strings.Split), + }, // split(s, sep) => [string] + "split_after": &tengo.UserFunction{ + Name: "split_after", + Value: FuncASSRSs(strings.SplitAfter), + }, // split_after(s, sep) => [string] + "split_after_n": &tengo.UserFunction{ + Name: "split_after_n", + Value: FuncASSIRSs(strings.SplitAfterN), + }, // split_after_n(s, sep, n) => [string] + "split_n": &tengo.UserFunction{ + Name: "split_n", + Value: FuncASSIRSs(strings.SplitN), + }, // split_n(s, sep, n) => [string] + "title": &tengo.UserFunction{ + Name: "title", + Value: FuncASRS(strings.Title), + }, // title(s) => string + "to_lower": &tengo.UserFunction{ + Name: "to_lower", + Value: FuncASRS(strings.ToLower), + }, // to_lower(s) => string + "to_title": &tengo.UserFunction{ + Name: "to_title", + Value: FuncASRS(strings.ToTitle), + }, // to_title(s) => string + "to_upper": &tengo.UserFunction{ + Name: "to_upper", + Value: FuncASRS(strings.ToUpper), + }, // to_upper(s) => string + "pad_left": &tengo.UserFunction{ + Name: "pad_left", + Value: textPadLeft, + }, // pad_left(s, pad_len, pad_with) => string + "pad_right": &tengo.UserFunction{ + Name: "pad_right", + Value: textPadRight, + }, // pad_right(s, pad_len, pad_with) => string + "trim": &tengo.UserFunction{ + Name: "trim", + Value: FuncASSRS(strings.Trim), + }, // trim(s, cutset) => string + "trim_left": &tengo.UserFunction{ + Name: "trim_left", + Value: FuncASSRS(strings.TrimLeft), + }, // trim_left(s, cutset) => string + "trim_prefix": &tengo.UserFunction{ + Name: "trim_prefix", + Value: FuncASSRS(strings.TrimPrefix), + }, // trim_prefix(s, prefix) => string + "trim_right": &tengo.UserFunction{ + Name: "trim_right", + Value: FuncASSRS(strings.TrimRight), + }, // trim_right(s, cutset) => string + "trim_space": &tengo.UserFunction{ + Name: "trim_space", + Value: FuncASRS(strings.TrimSpace), + }, // trim_space(s) => string + "trim_suffix": &tengo.UserFunction{ + Name: "trim_suffix", + Value: FuncASSRS(strings.TrimSuffix), + }, // trim_suffix(s, suffix) => string + "atoi": &tengo.UserFunction{ + Name: "atoi", + Value: FuncASRIE(strconv.Atoi), + }, // atoi(str) => int/error + "format_bool": &tengo.UserFunction{ + Name: "format_bool", + Value: textFormatBool, + }, // format_bool(b) => string + "format_float": &tengo.UserFunction{ + Name: "format_float", + Value: textFormatFloat, + }, // format_float(f, fmt, prec, bits) => string + "format_int": &tengo.UserFunction{ + Name: "format_int", + Value: textFormatInt, + }, // format_int(i, base) => string + "itoa": &tengo.UserFunction{ + Name: "itoa", + Value: FuncAIRS(strconv.Itoa), + }, // itoa(i) => string + "parse_bool": &tengo.UserFunction{ + Name: "parse_bool", + Value: textParseBool, + }, // parse_bool(str) => bool/error + "parse_float": &tengo.UserFunction{ + Name: "parse_float", + Value: textParseFloat, + }, // parse_float(str, bits) => float/error + "parse_int": &tengo.UserFunction{ + Name: "parse_int", + Value: textParseInt, + }, // parse_int(str, base, bits) => int/error + "quote": &tengo.UserFunction{ + Name: "quote", + Value: FuncASRS(strconv.Quote), + }, // quote(str) => string + "unquote": &tengo.UserFunction{ + Name: "unquote", + Value: FuncASRSE(strconv.Unquote), + }, // unquote(str) => string/error } -func textREMatch(args ...objects.Object) (ret objects.Object, err error) { +func textREMatch(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 2 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - s1, ok := objects.ToString(args[0]) + s1, ok := tengo.ToString(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), @@ -77,9 +217,9 @@ func textREMatch(args ...objects.Object) (ret objects.Object, err error) { return } - s2, ok := objects.ToString(args[1]) + s2, ok := tengo.ToString(args[1]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "second", Expected: "string(compatible)", Found: args[1].TypeName(), @@ -94,24 +234,24 @@ func textREMatch(args ...objects.Object) (ret objects.Object, err error) { } if matched { - ret = objects.TrueValue + ret = tengo.TrueValue } else { - ret = objects.FalseValue + ret = tengo.FalseValue } return } -func textREFind(args ...objects.Object) (ret objects.Object, err error) { +func textREFind(args ...tengo.Object) (ret tengo.Object, err error) { numArgs := len(args) if numArgs != 2 && numArgs != 3 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - s1, ok := objects.ToString(args[0]) + s1, ok := tengo.ToString(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), @@ -125,9 +265,9 @@ func textREFind(args ...objects.Object) (ret objects.Object, err error) { return } - s2, ok := objects.ToString(args[1]) + s2, ok := tengo.ToString(args[1]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "second", Expected: "string(compatible)", Found: args[1].TypeName(), @@ -138,27 +278,28 @@ func textREFind(args ...objects.Object) (ret objects.Object, err error) { if numArgs < 3 { m := re.FindStringSubmatchIndex(s2) if m == nil { - ret = objects.UndefinedValue + ret = tengo.UndefinedValue return } - arr := &objects.Array{} + arr := &tengo.Array{} for i := 0; i < len(m); i += 2 { - arr.Value = append(arr.Value, &objects.ImmutableMap{Value: map[string]objects.Object{ - "text": &objects.String{Value: s2[m[i]:m[i+1]]}, - "begin": &objects.Int{Value: int64(m[i])}, - "end": &objects.Int{Value: int64(m[i+1])}, - }}) + arr.Value = append(arr.Value, + &tengo.ImmutableMap{Value: map[string]tengo.Object{ + "text": &tengo.String{Value: s2[m[i]:m[i+1]]}, + "begin": &tengo.Int{Value: int64(m[i])}, + "end": &tengo.Int{Value: int64(m[i+1])}, + }}) } - ret = &objects.Array{Value: []objects.Object{arr}} + ret = &tengo.Array{Value: []tengo.Object{arr}} return } - i3, ok := objects.ToInt(args[2]) + i3, ok := tengo.ToInt(args[2]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "third", Expected: "int(compatible)", Found: args[2].TypeName(), @@ -167,19 +308,20 @@ func textREFind(args ...objects.Object) (ret objects.Object, err error) { } m := re.FindAllStringSubmatchIndex(s2, i3) if m == nil { - ret = objects.UndefinedValue + ret = tengo.UndefinedValue return } - arr := &objects.Array{} + arr := &tengo.Array{} for _, m := range m { - subMatch := &objects.Array{} + subMatch := &tengo.Array{} for i := 0; i < len(m); i += 2 { - subMatch.Value = append(subMatch.Value, &objects.ImmutableMap{Value: map[string]objects.Object{ - "text": &objects.String{Value: s2[m[i]:m[i+1]]}, - "begin": &objects.Int{Value: int64(m[i])}, - "end": &objects.Int{Value: int64(m[i+1])}, - }}) + subMatch.Value = append(subMatch.Value, + &tengo.ImmutableMap{Value: map[string]tengo.Object{ + "text": &tengo.String{Value: s2[m[i]:m[i+1]]}, + "begin": &tengo.Int{Value: int64(m[i])}, + "end": &tengo.Int{Value: int64(m[i+1])}, + }}) } arr.Value = append(arr.Value, subMatch) @@ -190,15 +332,15 @@ func textREFind(args ...objects.Object) (ret objects.Object, err error) { return } -func textREReplace(args ...objects.Object) (ret objects.Object, err error) { +func textREReplace(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 3 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - s1, ok := objects.ToString(args[0]) + s1, ok := tengo.ToString(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), @@ -206,9 +348,9 @@ func textREReplace(args ...objects.Object) (ret objects.Object, err error) { return } - s2, ok := objects.ToString(args[1]) + s2, ok := tengo.ToString(args[1]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "second", Expected: "string(compatible)", Found: args[1].TypeName(), @@ -216,9 +358,9 @@ func textREReplace(args ...objects.Object) (ret objects.Object, err error) { return } - s3, ok := objects.ToString(args[2]) + s3, ok := tengo.ToString(args[2]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "third", Expected: "string(compatible)", Found: args[2].TypeName(), @@ -232,25 +374,25 @@ func textREReplace(args ...objects.Object) (ret objects.Object, err error) { } else { s, ok := doTextRegexpReplace(re, s2, s3) if !ok { - return nil, objects.ErrStringLimit + return nil, tengo.ErrStringLimit } - ret = &objects.String{Value: s} + ret = &tengo.String{Value: s} } return } -func textRESplit(args ...objects.Object) (ret objects.Object, err error) { +func textRESplit(args ...tengo.Object) (ret tengo.Object, err error) { numArgs := len(args) if numArgs != 2 && numArgs != 3 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - s1, ok := objects.ToString(args[0]) + s1, ok := tengo.ToString(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), @@ -258,9 +400,9 @@ func textRESplit(args ...objects.Object) (ret objects.Object, err error) { return } - s2, ok := objects.ToString(args[1]) + s2, ok := tengo.ToString(args[1]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "second", Expected: "string(compatible)", Found: args[1].TypeName(), @@ -270,9 +412,9 @@ func textRESplit(args ...objects.Object) (ret objects.Object, err error) { var i3 = -1 if numArgs > 2 { - i3, ok = objects.ToInt(args[2]) + i3, ok = tengo.ToInt(args[2]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "third", Expected: "int(compatible)", Found: args[2].TypeName(), @@ -287,9 +429,9 @@ func textRESplit(args ...objects.Object) (ret objects.Object, err error) { return } - arr := &objects.Array{} + arr := &tengo.Array{} for _, s := range re.Split(s2, i3) { - arr.Value = append(arr.Value, &objects.String{Value: s}) + arr.Value = append(arr.Value, &tengo.String{Value: s}) } ret = arr @@ -297,15 +439,15 @@ func textRESplit(args ...objects.Object) (ret objects.Object, err error) { return } -func textRECompile(args ...objects.Object) (ret objects.Object, err error) { +func textRECompile(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 1 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - s1, ok := objects.ToString(args[0]) + s1, ok := tengo.ToString(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), @@ -323,15 +465,15 @@ func textRECompile(args ...objects.Object) (ret objects.Object, err error) { return } -func textReplace(args ...objects.Object) (ret objects.Object, err error) { +func textReplace(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 4 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - s1, ok := objects.ToString(args[0]) + s1, ok := tengo.ToString(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), @@ -339,9 +481,9 @@ func textReplace(args ...objects.Object) (ret objects.Object, err error) { return } - s2, ok := objects.ToString(args[1]) + s2, ok := tengo.ToString(args[1]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "second", Expected: "string(compatible)", Found: args[1].TypeName(), @@ -349,9 +491,9 @@ func textReplace(args ...objects.Object) (ret objects.Object, err error) { return } - s3, ok := objects.ToString(args[2]) + s3, ok := tengo.ToString(args[2]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "third", Expected: "string(compatible)", Found: args[2].TypeName(), @@ -359,9 +501,9 @@ func textReplace(args ...objects.Object) (ret objects.Object, err error) { return } - i4, ok := objects.ToInt(args[3]) + i4, ok := tengo.ToInt(args[3]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "fourth", Expected: "int(compatible)", Found: args[3].TypeName(), @@ -371,25 +513,25 @@ func textReplace(args ...objects.Object) (ret objects.Object, err error) { s, ok := doTextReplace(s1, s2, s3, i4) if !ok { - err = objects.ErrStringLimit + err = tengo.ErrStringLimit return } - ret = &objects.String{Value: s} + ret = &tengo.String{Value: s} return } -func textSubstring(args ...objects.Object) (ret objects.Object, err error) { +func textSubstring(args ...tengo.Object) (ret tengo.Object, err error) { argslen := len(args) if argslen != 2 && argslen != 3 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - s1, ok := objects.ToString(args[0]) + s1, ok := tengo.ToString(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), @@ -397,9 +539,9 @@ func textSubstring(args ...objects.Object) (ret objects.Object, err error) { return } - i2, ok := objects.ToInt(args[1]) + i2, ok := tengo.ToInt(args[1]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "second", Expected: "int(compatible)", Found: args[1].TypeName(), @@ -410,9 +552,9 @@ func textSubstring(args ...objects.Object) (ret objects.Object, err error) { strlen := len(s1) i3 := strlen if argslen == 3 { - i3, ok = objects.ToInt(args[2]) + i3, ok = tengo.ToInt(args[2]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "third", Expected: "int(compatible)", Found: args[2].TypeName(), @@ -422,7 +564,7 @@ func textSubstring(args ...objects.Object) (ret objects.Object, err error) { } if i2 > i3 { - err = objects.ErrInvalidIndexType + err = tengo.ErrInvalidIndexType return } @@ -438,21 +580,21 @@ func textSubstring(args ...objects.Object) (ret objects.Object, err error) { i3 = strlen } - ret = &objects.String{Value: s1[i2:i3]} + ret = &tengo.String{Value: s1[i2:i3]} return } -func textPadLeft(args ...objects.Object) (ret objects.Object, err error) { +func textPadLeft(args ...tengo.Object) (ret tengo.Object, err error) { argslen := len(args) if argslen != 2 && argslen != 3 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - s1, ok := objects.ToString(args[0]) + s1, ok := tengo.ToString(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), @@ -460,9 +602,9 @@ func textPadLeft(args ...objects.Object) (ret objects.Object, err error) { return } - i2, ok := objects.ToInt(args[1]) + i2, ok := tengo.ToInt(args[1]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "second", Expected: "int(compatible)", Found: args[1].TypeName(), @@ -471,20 +613,20 @@ func textPadLeft(args ...objects.Object) (ret objects.Object, err error) { } if i2 > tengo.MaxStringLen { - return nil, objects.ErrStringLimit + return nil, tengo.ErrStringLimit } sLen := len(s1) if sLen >= i2 { - ret = &objects.String{Value: s1} + ret = &tengo.String{Value: s1} return } s3 := " " if argslen == 3 { - s3, ok = objects.ToString(args[2]) + s3, ok = tengo.ToString(args[2]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "third", Expected: "string(compatible)", Found: args[2].TypeName(), @@ -495,27 +637,27 @@ func textPadLeft(args ...objects.Object) (ret objects.Object, err error) { padStrLen := len(s3) if padStrLen == 0 { - ret = &objects.String{Value: s1} + ret = &tengo.String{Value: s1} return } padCount := ((i2 - padStrLen) / padStrLen) + 1 - retStr := strings.Repeat(s3, int(padCount)) + s1 - ret = &objects.String{Value: retStr[len(retStr)-i2:]} + retStr := strings.Repeat(s3, padCount) + s1 + ret = &tengo.String{Value: retStr[len(retStr)-i2:]} return } -func textPadRight(args ...objects.Object) (ret objects.Object, err error) { +func textPadRight(args ...tengo.Object) (ret tengo.Object, err error) { argslen := len(args) if argslen != 2 && argslen != 3 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - s1, ok := objects.ToString(args[0]) + s1, ok := tengo.ToString(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), @@ -523,9 +665,9 @@ func textPadRight(args ...objects.Object) (ret objects.Object, err error) { return } - i2, ok := objects.ToInt(args[1]) + i2, ok := tengo.ToInt(args[1]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "second", Expected: "int(compatible)", Found: args[1].TypeName(), @@ -534,20 +676,20 @@ func textPadRight(args ...objects.Object) (ret objects.Object, err error) { } if i2 > tengo.MaxStringLen { - return nil, objects.ErrStringLimit + return nil, tengo.ErrStringLimit } sLen := len(s1) if sLen >= i2 { - ret = &objects.String{Value: s1} + ret = &tengo.String{Value: s1} return } s3 := " " if argslen == 3 { - s3, ok = objects.ToString(args[2]) + s3, ok = tengo.ToString(args[2]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "third", Expected: "string(compatible)", Found: args[2].TypeName(), @@ -558,34 +700,34 @@ func textPadRight(args ...objects.Object) (ret objects.Object, err error) { padStrLen := len(s3) if padStrLen == 0 { - ret = &objects.String{Value: s1} + ret = &tengo.String{Value: s1} return } padCount := ((i2 - padStrLen) / padStrLen) + 1 - retStr := s1 + strings.Repeat(s3, int(padCount)) - ret = &objects.String{Value: retStr[:i2]} + retStr := s1 + strings.Repeat(s3, padCount) + ret = &tengo.String{Value: retStr[:i2]} return } -func textRepeat(args ...objects.Object) (ret objects.Object, err error) { +func textRepeat(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 2 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } - s1, ok := objects.ToString(args[0]) + s1, ok := tengo.ToString(args[0]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), } } - i2, ok := objects.ToInt(args[1]) + i2, ok := tengo.ToInt(args[1]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "second", Expected: "int(compatible)", Found: args[1].TypeName(), @@ -593,25 +735,25 @@ func textRepeat(args ...objects.Object) (ret objects.Object, err error) { } if len(s1)*i2 > tengo.MaxStringLen { - return nil, objects.ErrStringLimit + return nil, tengo.ErrStringLimit } - return &objects.String{Value: strings.Repeat(s1, i2)}, nil + return &tengo.String{Value: strings.Repeat(s1, i2)}, nil } -func textJoin(args ...objects.Object) (ret objects.Object, err error) { +func textJoin(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 2 { - return nil, objects.ErrWrongNumArguments + return nil, tengo.ErrWrongNumArguments } var slen int var ss1 []string switch arg0 := args[0].(type) { - case *objects.Array: + case *tengo.Array: for idx, a := range arg0.Value { - as, ok := objects.ToString(a) + as, ok := tengo.ToString(a) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: fmt.Sprintf("first[%d]", idx), Expected: "string(compatible)", Found: a.TypeName(), @@ -620,11 +762,11 @@ func textJoin(args ...objects.Object) (ret objects.Object, err error) { slen += len(as) ss1 = append(ss1, as) } - case *objects.ImmutableArray: + case *tengo.ImmutableArray: for idx, a := range arg0.Value { - as, ok := objects.ToString(a) + as, ok := tengo.ToString(a) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: fmt.Sprintf("first[%d]", idx), Expected: "string(compatible)", Found: a.TypeName(), @@ -634,16 +776,16 @@ func textJoin(args ...objects.Object) (ret objects.Object, err error) { ss1 = append(ss1, as) } default: - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "first", Expected: "array", Found: args[0].TypeName(), } } - s2, ok := objects.ToString(args[1]) + s2, ok := tengo.ToString(args[1]) if !ok { - return nil, objects.ErrInvalidArgumentType{ + return nil, tengo.ErrInvalidArgumentType{ Name: "second", Expected: "string(compatible)", Found: args[1].TypeName(), @@ -652,21 +794,21 @@ func textJoin(args ...objects.Object) (ret objects.Object, err error) { // make sure output length does not exceed the limit if slen+len(s2)*(len(ss1)-1) > tengo.MaxStringLen { - return nil, objects.ErrStringLimit + return nil, tengo.ErrStringLimit } - return &objects.String{Value: strings.Join(ss1, s2)}, nil + return &tengo.String{Value: strings.Join(ss1, s2)}, nil } -func textFormatBool(args ...objects.Object) (ret objects.Object, err error) { +func textFormatBool(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 1 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - b1, ok := args[0].(*objects.Bool) + b1, ok := args[0].(*tengo.Bool) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "bool", Found: args[0].TypeName(), @@ -674,24 +816,24 @@ func textFormatBool(args ...objects.Object) (ret objects.Object, err error) { return } - if b1 == objects.TrueValue { - ret = &objects.String{Value: "true"} + if b1 == tengo.TrueValue { + ret = &tengo.String{Value: "true"} } else { - ret = &objects.String{Value: "false"} + ret = &tengo.String{Value: "false"} } return } -func textFormatFloat(args ...objects.Object) (ret objects.Object, err error) { +func textFormatFloat(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 4 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - f1, ok := args[0].(*objects.Float) + f1, ok := args[0].(*tengo.Float) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "float", Found: args[0].TypeName(), @@ -699,9 +841,9 @@ func textFormatFloat(args ...objects.Object) (ret objects.Object, err error) { return } - s2, ok := objects.ToString(args[1]) + s2, ok := tengo.ToString(args[1]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "second", Expected: "string(compatible)", Found: args[1].TypeName(), @@ -709,9 +851,9 @@ func textFormatFloat(args ...objects.Object) (ret objects.Object, err error) { return } - i3, ok := objects.ToInt(args[2]) + i3, ok := tengo.ToInt(args[2]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "third", Expected: "int(compatible)", Found: args[2].TypeName(), @@ -719,9 +861,9 @@ func textFormatFloat(args ...objects.Object) (ret objects.Object, err error) { return } - i4, ok := objects.ToInt(args[3]) + i4, ok := tengo.ToInt(args[3]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "fourth", Expected: "int(compatible)", Found: args[3].TypeName(), @@ -729,20 +871,20 @@ func textFormatFloat(args ...objects.Object) (ret objects.Object, err error) { return } - ret = &objects.String{Value: strconv.FormatFloat(f1.Value, s2[0], i3, i4)} + ret = &tengo.String{Value: strconv.FormatFloat(f1.Value, s2[0], i3, i4)} return } -func textFormatInt(args ...objects.Object) (ret objects.Object, err error) { +func textFormatInt(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 2 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - i1, ok := args[0].(*objects.Int) + i1, ok := args[0].(*tengo.Int) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "int", Found: args[0].TypeName(), @@ -750,9 +892,9 @@ func textFormatInt(args ...objects.Object) (ret objects.Object, err error) { return } - i2, ok := objects.ToInt(args[1]) + i2, ok := tengo.ToInt(args[1]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "second", Expected: "int(compatible)", Found: args[1].TypeName(), @@ -760,20 +902,20 @@ func textFormatInt(args ...objects.Object) (ret objects.Object, err error) { return } - ret = &objects.String{Value: strconv.FormatInt(i1.Value, i2)} + ret = &tengo.String{Value: strconv.FormatInt(i1.Value, i2)} return } -func textParseBool(args ...objects.Object) (ret objects.Object, err error) { +func textParseBool(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 1 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - s1, ok := args[0].(*objects.String) + s1, ok := args[0].(*tengo.String) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string", Found: args[0].TypeName(), @@ -788,23 +930,23 @@ func textParseBool(args ...objects.Object) (ret objects.Object, err error) { } if parsed { - ret = objects.TrueValue + ret = tengo.TrueValue } else { - ret = objects.FalseValue + ret = tengo.FalseValue } return } -func textParseFloat(args ...objects.Object) (ret objects.Object, err error) { +func textParseFloat(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 2 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - s1, ok := args[0].(*objects.String) + s1, ok := args[0].(*tengo.String) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string", Found: args[0].TypeName(), @@ -812,9 +954,9 @@ func textParseFloat(args ...objects.Object) (ret objects.Object, err error) { return } - i2, ok := objects.ToInt(args[1]) + i2, ok := tengo.ToInt(args[1]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "second", Expected: "int(compatible)", Found: args[1].TypeName(), @@ -828,20 +970,20 @@ func textParseFloat(args ...objects.Object) (ret objects.Object, err error) { return } - ret = &objects.Float{Value: parsed} + ret = &tengo.Float{Value: parsed} return } -func textParseInt(args ...objects.Object) (ret objects.Object, err error) { +func textParseInt(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 3 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - s1, ok := args[0].(*objects.String) + s1, ok := args[0].(*tengo.String) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string", Found: args[0].TypeName(), @@ -849,9 +991,9 @@ func textParseInt(args ...objects.Object) (ret objects.Object, err error) { return } - i2, ok := objects.ToInt(args[1]) + i2, ok := tengo.ToInt(args[1]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "second", Expected: "int(compatible)", Found: args[1].TypeName(), @@ -859,9 +1001,9 @@ func textParseInt(args ...objects.Object) (ret objects.Object, err error) { return } - i3, ok := objects.ToInt(args[2]) + i3, ok := tengo.ToInt(args[2]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "third", Expected: "int(compatible)", Found: args[2].TypeName(), @@ -875,7 +1017,7 @@ func textParseInt(args ...objects.Object) (ret objects.Object, err error) { return } - ret = &objects.Int{Value: parsed} + ret = &tengo.Int{Value: parsed} return } diff --git a/stdlib/text_regexp.go b/stdlib/text_regexp.go index 16f135b..f2efe37 100644 --- a/stdlib/text_regexp.go +++ b/stdlib/text_regexp.go @@ -4,23 +4,25 @@ import ( "regexp" "github.com/d5/tengo" - "github.com/d5/tengo/objects" ) -func makeTextRegexp(re *regexp.Regexp) *objects.ImmutableMap { - return &objects.ImmutableMap{ - Value: map[string]objects.Object{ +func makeTextRegexp(re *regexp.Regexp) *tengo.ImmutableMap { + return &tengo.ImmutableMap{ + Value: map[string]tengo.Object{ // match(text) => bool - "match": &objects.UserFunction{ - Value: func(args ...objects.Object) (ret objects.Object, err error) { + "match": &tengo.UserFunction{ + Value: func(args ...tengo.Object) ( + ret tengo.Object, + err error, + ) { if len(args) != 1 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - s1, ok := objects.ToString(args[0]) + s1, ok := tengo.ToString(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), @@ -29,9 +31,9 @@ func makeTextRegexp(re *regexp.Regexp) *objects.ImmutableMap { } if re.MatchString(s1) { - ret = objects.TrueValue + ret = tengo.TrueValue } else { - ret = objects.FalseValue + ret = tengo.FalseValue } return @@ -40,17 +42,20 @@ func makeTextRegexp(re *regexp.Regexp) *objects.ImmutableMap { // find(text) => array(array({text:,begin:,end:}))/undefined // find(text, maxCount) => array(array({text:,begin:,end:}))/undefined - "find": &objects.UserFunction{ - Value: func(args ...objects.Object) (ret objects.Object, err error) { + "find": &tengo.UserFunction{ + Value: func(args ...tengo.Object) ( + ret tengo.Object, + err error, + ) { numArgs := len(args) if numArgs != 1 && numArgs != 2 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - s1, ok := objects.ToString(args[0]) + s1, ok := tengo.ToString(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), @@ -61,27 +66,35 @@ func makeTextRegexp(re *regexp.Regexp) *objects.ImmutableMap { if numArgs == 1 { m := re.FindStringSubmatchIndex(s1) if m == nil { - ret = objects.UndefinedValue + ret = tengo.UndefinedValue return } - arr := &objects.Array{} + arr := &tengo.Array{} for i := 0; i < len(m); i += 2 { - arr.Value = append(arr.Value, &objects.ImmutableMap{Value: map[string]objects.Object{ - "text": &objects.String{Value: s1[m[i]:m[i+1]]}, - "begin": &objects.Int{Value: int64(m[i])}, - "end": &objects.Int{Value: int64(m[i+1])}, - }}) + arr.Value = append(arr.Value, + &tengo.ImmutableMap{ + Value: map[string]tengo.Object{ + "text": &tengo.String{ + Value: s1[m[i]:m[i+1]], + }, + "begin": &tengo.Int{ + Value: int64(m[i]), + }, + "end": &tengo.Int{ + Value: int64(m[i+1]), + }, + }}) } - ret = &objects.Array{Value: []objects.Object{arr}} + ret = &tengo.Array{Value: []tengo.Object{arr}} return } - i2, ok := objects.ToInt(args[1]) + i2, ok := tengo.ToInt(args[1]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "second", Expected: "int(compatible)", Found: args[1].TypeName(), @@ -90,19 +103,27 @@ func makeTextRegexp(re *regexp.Regexp) *objects.ImmutableMap { } m := re.FindAllStringSubmatchIndex(s1, i2) if m == nil { - ret = objects.UndefinedValue + ret = tengo.UndefinedValue return } - arr := &objects.Array{} + arr := &tengo.Array{} for _, m := range m { - subMatch := &objects.Array{} + subMatch := &tengo.Array{} for i := 0; i < len(m); i += 2 { - subMatch.Value = append(subMatch.Value, &objects.ImmutableMap{Value: map[string]objects.Object{ - "text": &objects.String{Value: s1[m[i]:m[i+1]]}, - "begin": &objects.Int{Value: int64(m[i])}, - "end": &objects.Int{Value: int64(m[i+1])}, - }}) + subMatch.Value = append(subMatch.Value, + &tengo.ImmutableMap{ + Value: map[string]tengo.Object{ + "text": &tengo.String{ + Value: s1[m[i]:m[i+1]], + }, + "begin": &tengo.Int{ + Value: int64(m[i]), + }, + "end": &tengo.Int{ + Value: int64(m[i+1]), + }, + }}) } arr.Value = append(arr.Value, subMatch) @@ -115,16 +136,19 @@ func makeTextRegexp(re *regexp.Regexp) *objects.ImmutableMap { }, // replace(src, repl) => string - "replace": &objects.UserFunction{ - Value: func(args ...objects.Object) (ret objects.Object, err error) { + "replace": &tengo.UserFunction{ + Value: func(args ...tengo.Object) ( + ret tengo.Object, + err error, + ) { if len(args) != 2 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - s1, ok := objects.ToString(args[0]) + s1, ok := tengo.ToString(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), @@ -132,9 +156,9 @@ func makeTextRegexp(re *regexp.Regexp) *objects.ImmutableMap { return } - s2, ok := objects.ToString(args[1]) + s2, ok := tengo.ToString(args[1]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "second", Expected: "string(compatible)", Found: args[1].TypeName(), @@ -144,10 +168,10 @@ func makeTextRegexp(re *regexp.Regexp) *objects.ImmutableMap { s, ok := doTextRegexpReplace(re, s1, s2) if !ok { - return nil, objects.ErrStringLimit + return nil, tengo.ErrStringLimit } - ret = &objects.String{Value: s} + ret = &tengo.String{Value: s} return }, @@ -155,17 +179,20 @@ func makeTextRegexp(re *regexp.Regexp) *objects.ImmutableMap { // split(text) => array(string) // split(text, maxCount) => array(string) - "split": &objects.UserFunction{ - Value: func(args ...objects.Object) (ret objects.Object, err error) { + "split": &tengo.UserFunction{ + Value: func(args ...tengo.Object) ( + ret tengo.Object, + err error, + ) { numArgs := len(args) if numArgs != 1 && numArgs != 2 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - s1, ok := objects.ToString(args[0]) + s1, ok := tengo.ToString(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), @@ -175,9 +202,9 @@ func makeTextRegexp(re *regexp.Regexp) *objects.ImmutableMap { var i2 = -1 if numArgs > 1 { - i2, ok = objects.ToInt(args[1]) + i2, ok = tengo.ToInt(args[1]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "second", Expected: "int(compatible)", Found: args[1].TypeName(), @@ -186,9 +213,10 @@ func makeTextRegexp(re *regexp.Regexp) *objects.ImmutableMap { } } - arr := &objects.Array{} + arr := &tengo.Array{} for _, s := range re.Split(s1, i2) { - arr.Value = append(arr.Value, &objects.String{Value: s}) + arr.Value = append(arr.Value, + &tengo.String{Value: s}) } ret = arr @@ -204,15 +232,12 @@ func makeTextRegexp(re *regexp.Regexp) *objects.ImmutableMap { func doTextRegexpReplace(re *regexp.Regexp, src, repl string) (string, bool) { idx := 0 out := "" - for _, m := range re.FindAllStringSubmatchIndex(src, -1) { var exp []byte exp = re.ExpandString(exp, repl, src, m) - if len(out)+m[0]-idx+len(exp) > tengo.MaxStringLen { return "", false } - out += src[idx:m[0]] + string(exp) idx = m[1] } @@ -220,9 +245,7 @@ func doTextRegexpReplace(re *regexp.Regexp, src, repl string) (string, bool) { if len(out)+len(src)-idx > tengo.MaxStringLen { return "", false } - out += src[idx:] } - - return string(out), true + return out, true } diff --git a/stdlib/text_test.go b/stdlib/text_test.go index f8e14ee..5299760 100644 --- a/stdlib/text_test.go +++ b/stdlib/text_test.go @@ -5,7 +5,6 @@ import ( "testing" "github.com/d5/tengo" - "github.com/d5/tengo/objects" ) func TestTextRE(t *testing.T) { @@ -22,8 +21,10 @@ func TestTextRE(t *testing.T) { {"^b", "abc"}, } { expected := regexp.MustCompile(d.pattern).MatchString(d.text) - module(t, "text").call("re_match", d.pattern, d.text).expect(expected, "pattern: %q, src: %q", d.pattern, d.text) - module(t, "text").call("re_compile", d.pattern).call("match", d.text).expect(expected, "patter: %q, src: %q", d.pattern, d.text) + module(t, "text").call("re_match", d.pattern, d.text). + expect(expected, "pattern: %q, src: %q", d.pattern, d.text) + module(t, "text").call("re_compile", d.pattern).call("match", d.text). + expect(expected, "patter: %q, src: %q", d.pattern, d.text) } // re_find(pattern, text) @@ -32,7 +33,7 @@ func TestTextRE(t *testing.T) { text string expected interface{} }{ - {"a(b)", "", objects.UndefinedValue}, + {"a(b)", "", tengo.UndefinedValue}, {"a(b)", "ab", ARR{ ARR{ IMAP{"text": "ab", "begin": 0, "end": 2}, @@ -53,8 +54,10 @@ func TestTextRE(t *testing.T) { }, }}, } { - module(t, "text").call("re_find", d.pattern, d.text).expect(d.expected, "pattern: %q, text: %q", d.pattern, d.text) - module(t, "text").call("re_compile", d.pattern).call("find", d.text).expect(d.expected, "pattern: %q, text: %q", d.pattern, d.text) + module(t, "text").call("re_find", d.pattern, d.text). + expect(d.expected, "pattern: %q, text: %q", d.pattern, d.text) + module(t, "text").call("re_compile", d.pattern).call("find", d.text). + expect(d.expected, "pattern: %q, text: %q", d.pattern, d.text) } // re_find(pattern, text, count)) @@ -64,7 +67,7 @@ func TestTextRE(t *testing.T) { count int expected interface{} }{ - {"a(b)", "", -1, objects.UndefinedValue}, + {"a(b)", "", -1, tengo.UndefinedValue}, {"a(b)", "ab", -1, ARR{ ARR{ IMAP{"text": "ab", "begin": 0, "end": 2}, @@ -93,7 +96,7 @@ func TestTextRE(t *testing.T) { IMAP{"text": "c", "begin": 9, "end": 10}, }, }}, - {"(a)b(c)d", "abcdefgabcd", 0, objects.UndefinedValue}, + {"(a)b(c)d", "abcdefgabcd", 0, tengo.UndefinedValue}, {"(a)b(c)d", "abcdefgabcd", 1, ARR{ ARR{ IMAP{"text": "abcd", "begin": 0, "end": 4}, @@ -102,8 +105,11 @@ func TestTextRE(t *testing.T) { }, }}, } { - module(t, "text").call("re_find", d.pattern, d.text, d.count).expect(d.expected, "pattern: %q, text: %q", d.pattern, d.text) - module(t, "text").call("re_compile", d.pattern).call("find", d.text, d.count).expect(d.expected, "pattern: %q, text: %q", d.pattern, d.text) + module(t, "text").call("re_find", d.pattern, d.text, d.count). + expect(d.expected, "pattern: %q, text: %q", d.pattern, d.text) + module(t, "text").call("re_compile", d.pattern). + call("find", d.text, d.count). + expect(d.expected, "pattern: %q, text: %q", d.pattern, d.text) } // re_replace(pattern, text, repl) @@ -126,9 +132,15 @@ func TestTextRE(t *testing.T) { {"((일)(2)3)", "일23\n일이3\n일23", "$1$2$3"}, {"(a(b)c)", "abc\nabc\nabc", "$1$2"}, } { - expected := regexp.MustCompile(d.pattern).ReplaceAllString(d.text, d.repl) - module(t, "text").call("re_replace", d.pattern, d.text, d.repl).expect(expected, "pattern: %q, text: %q, repl: %q", d.pattern, d.text, d.repl) - module(t, "text").call("re_compile", d.pattern).call("replace", d.text, d.repl).expect(expected, "pattern: %q, text: %q, repl: %q", d.pattern, d.text, d.repl) + expected := regexp.MustCompile(d.pattern). + ReplaceAllString(d.text, d.repl) + module(t, "text").call("re_replace", d.pattern, d.text, d.repl). + expect(expected, "pattern: %q, text: %q, repl: %q", + d.pattern, d.text, d.repl) + module(t, "text").call("re_compile", d.pattern). + call("replace", d.text, d.repl). + expect(expected, "pattern: %q, text: %q, repl: %q", + d.pattern, d.text, d.repl) } // re_split(pattern, text) @@ -145,8 +157,10 @@ func TestTextRE(t *testing.T) { for _, ex := range regexp.MustCompile(d.pattern).Split(d.text, -1) { expected = append(expected, ex) } - module(t, "text").call("re_split", d.pattern, d.text).expect(expected, "pattern: %q, text: %q", d.pattern, d.text) - module(t, "text").call("re_compile", d.pattern).call("split", d.text).expect(expected, "pattern: %q, text: %q", d.pattern, d.text) + module(t, "text").call("re_split", d.pattern, d.text). + expect(expected, "pattern: %q, text: %q", d.pattern, d.text) + module(t, "text").call("re_compile", d.pattern).call("split", d.text). + expect(expected, "pattern: %q, text: %q", d.pattern, d.text) } // re_split(pattern, text, count)) @@ -171,8 +185,11 @@ func TestTextRE(t *testing.T) { for _, ex := range regexp.MustCompile(d.pattern).Split(d.text, d.count) { expected = append(expected, ex) } - module(t, "text").call("re_split", d.pattern, d.text, d.count).expect(expected, "pattern: %q, text: %q", d.pattern, d.text) - module(t, "text").call("re_compile", d.pattern).call("split", d.text, d.count).expect(expected, "pattern: %q, text: %q", d.pattern, d.text) + module(t, "text").call("re_split", d.pattern, d.text, d.count). + expect(expected, "pattern: %q, text: %q", d.pattern, d.text) + module(t, "text").call("re_compile", d.pattern). + call("split", d.text, d.count). + expect(expected, "pattern: %q, text: %q", d.pattern, d.text) } } @@ -219,19 +236,31 @@ func TestReplaceLimit(t *testing.T) { defer func() { tengo.MaxStringLen = curMaxStringLen }() tengo.MaxStringLen = 12 - module(t, "text").call("replace", "123456789012", "1", "x", -1).expect("x234567890x2") - module(t, "text").call("replace", "123456789012", "12", "x", -1).expect("x34567890x") - module(t, "text").call("replace", "123456789012", "1", "xy", -1).expectError() - module(t, "text").call("replace", "123456789012", "0", "xy", -1).expectError() - module(t, "text").call("replace", "123456789012", "012", "xyz", -1).expect("123456789xyz") - module(t, "text").call("replace", "123456789012", "012", "xyzz", -1).expectError() + module(t, "text").call("replace", "123456789012", "1", "x", -1). + expect("x234567890x2") + module(t, "text").call("replace", "123456789012", "12", "x", -1). + expect("x34567890x") + module(t, "text").call("replace", "123456789012", "1", "xy", -1). + expectError() + module(t, "text").call("replace", "123456789012", "0", "xy", -1). + expectError() + module(t, "text").call("replace", "123456789012", "012", "xyz", -1). + expect("123456789xyz") + module(t, "text").call("replace", "123456789012", "012", "xyzz", -1). + expectError() - module(t, "text").call("re_replace", "1", "123456789012", "x").expect("x234567890x2") - module(t, "text").call("re_replace", "12", "123456789012", "x").expect("x34567890x") - module(t, "text").call("re_replace", "1", "123456789012", "xy").expectError() - module(t, "text").call("re_replace", "1(2)", "123456789012", "x$1").expect("x234567890x2") - module(t, "text").call("re_replace", "(1)(2)", "123456789012", "$2$1").expect("213456789021") - module(t, "text").call("re_replace", "(1)(2)", "123456789012", "${2}${1}x").expectError() + module(t, "text").call("re_replace", "1", "123456789012", "x"). + expect("x234567890x2") + module(t, "text").call("re_replace", "12", "123456789012", "x"). + expect("x34567890x") + module(t, "text").call("re_replace", "1", "123456789012", "xy"). + expectError() + module(t, "text").call("re_replace", "1(2)", "123456789012", "x$1"). + expect("x234567890x2") + module(t, "text").call("re_replace", "(1)(2)", "123456789012", "$2$1"). + expect("213456789021") + module(t, "text").call("re_replace", "(1)(2)", "123456789012", "${2}${1}x"). + expectError() } func TestTextRepeat(t *testing.T) { @@ -239,10 +268,14 @@ func TestTextRepeat(t *testing.T) { defer func() { tengo.MaxStringLen = curMaxStringLen }() tengo.MaxStringLen = 12 - module(t, "text").call("repeat", "1234", "3").expect("123412341234") - module(t, "text").call("repeat", "1234", "4").expectError() - module(t, "text").call("repeat", "1", "12").expect("111111111111") - module(t, "text").call("repeat", "1", "13").expectError() + module(t, "text").call("repeat", "1234", "3"). + expect("123412341234") + module(t, "text").call("repeat", "1234", "4"). + expectError() + module(t, "text").call("repeat", "1", "12"). + expect("111111111111") + module(t, "text").call("repeat", "1", "13"). + expectError() } func TestSubstr(t *testing.T) { diff --git a/stdlib/times.go b/stdlib/times.go index 111c877..3440e79 100644 --- a/stdlib/times.go +++ b/stdlib/times.go @@ -4,89 +4,193 @@ import ( "time" "github.com/d5/tengo" - "github.com/d5/tengo/objects" ) -var timesModule = map[string]objects.Object{ - "format_ansic": &objects.String{Value: time.ANSIC}, - "format_unix_date": &objects.String{Value: time.UnixDate}, - "format_ruby_date": &objects.String{Value: time.RubyDate}, - "format_rfc822": &objects.String{Value: time.RFC822}, - "format_rfc822z": &objects.String{Value: time.RFC822Z}, - "format_rfc850": &objects.String{Value: time.RFC850}, - "format_rfc1123": &objects.String{Value: time.RFC1123}, - "format_rfc1123z": &objects.String{Value: time.RFC1123Z}, - "format_rfc3339": &objects.String{Value: time.RFC3339}, - "format_rfc3339_nano": &objects.String{Value: time.RFC3339Nano}, - "format_kitchen": &objects.String{Value: time.Kitchen}, - "format_stamp": &objects.String{Value: time.Stamp}, - "format_stamp_milli": &objects.String{Value: time.StampMilli}, - "format_stamp_micro": &objects.String{Value: time.StampMicro}, - "format_stamp_nano": &objects.String{Value: time.StampNano}, - "nanosecond": &objects.Int{Value: int64(time.Nanosecond)}, - "microsecond": &objects.Int{Value: int64(time.Microsecond)}, - "millisecond": &objects.Int{Value: int64(time.Millisecond)}, - "second": &objects.Int{Value: int64(time.Second)}, - "minute": &objects.Int{Value: int64(time.Minute)}, - "hour": &objects.Int{Value: int64(time.Hour)}, - "january": &objects.Int{Value: int64(time.January)}, - "february": &objects.Int{Value: int64(time.February)}, - "march": &objects.Int{Value: int64(time.March)}, - "april": &objects.Int{Value: int64(time.April)}, - "may": &objects.Int{Value: int64(time.May)}, - "june": &objects.Int{Value: int64(time.June)}, - "july": &objects.Int{Value: int64(time.July)}, - "august": &objects.Int{Value: int64(time.August)}, - "september": &objects.Int{Value: int64(time.September)}, - "october": &objects.Int{Value: int64(time.October)}, - "november": &objects.Int{Value: int64(time.November)}, - "december": &objects.Int{Value: int64(time.December)}, - "sleep": &objects.UserFunction{Name: "sleep", Value: timesSleep}, // sleep(int) - "parse_duration": &objects.UserFunction{Name: "parse_duration", Value: timesParseDuration}, // parse_duration(str) => int - "since": &objects.UserFunction{Name: "since", Value: timesSince}, // since(time) => int - "until": &objects.UserFunction{Name: "until", Value: timesUntil}, // until(time) => int - "duration_hours": &objects.UserFunction{Name: "duration_hours", Value: timesDurationHours}, // duration_hours(int) => float - "duration_minutes": &objects.UserFunction{Name: "duration_minutes", Value: timesDurationMinutes}, // duration_minutes(int) => float - "duration_nanoseconds": &objects.UserFunction{Name: "duration_nanoseconds", Value: timesDurationNanoseconds}, // duration_nanoseconds(int) => int - "duration_seconds": &objects.UserFunction{Name: "duration_seconds", Value: timesDurationSeconds}, // duration_seconds(int) => float - "duration_string": &objects.UserFunction{Name: "duration_string", Value: timesDurationString}, // duration_string(int) => string - "month_string": &objects.UserFunction{Name: "month_string", Value: timesMonthString}, // month_string(int) => string - "date": &objects.UserFunction{Name: "date", Value: timesDate}, // date(year, month, day, hour, min, sec, nsec) => time - "now": &objects.UserFunction{Name: "now", Value: timesNow}, // now() => time - "parse": &objects.UserFunction{Name: "parse", Value: timesParse}, // parse(format, str) => time - "unix": &objects.UserFunction{Name: "unix", Value: timesUnix}, // unix(sec, nsec) => time - "add": &objects.UserFunction{Name: "add", Value: timesAdd}, // add(time, int) => time - "add_date": &objects.UserFunction{Name: "add_date", Value: timesAddDate}, // add_date(time, years, months, days) => time - "sub": &objects.UserFunction{Name: "sub", Value: timesSub}, // sub(t time, u time) => int - "after": &objects.UserFunction{Name: "after", Value: timesAfter}, // after(t time, u time) => bool - "before": &objects.UserFunction{Name: "before", Value: timesBefore}, // before(t time, u time) => bool - "time_year": &objects.UserFunction{Name: "time_year", Value: timesTimeYear}, // time_year(time) => int - "time_month": &objects.UserFunction{Name: "time_month", Value: timesTimeMonth}, // time_month(time) => int - "time_day": &objects.UserFunction{Name: "time_day", Value: timesTimeDay}, // time_day(time) => int - "time_weekday": &objects.UserFunction{Name: "time_weekday", Value: timesTimeWeekday}, // time_weekday(time) => int - "time_hour": &objects.UserFunction{Name: "time_hour", Value: timesTimeHour}, // time_hour(time) => int - "time_minute": &objects.UserFunction{Name: "time_minute", Value: timesTimeMinute}, // time_minute(time) => int - "time_second": &objects.UserFunction{Name: "time_second", Value: timesTimeSecond}, // time_second(time) => int - "time_nanosecond": &objects.UserFunction{Name: "time_nanosecond", Value: timesTimeNanosecond}, // time_nanosecond(time) => int - "time_unix": &objects.UserFunction{Name: "time_unix", Value: timesTimeUnix}, // time_unix(time) => int - "time_unix_nano": &objects.UserFunction{Name: "time_unix_nano", Value: timesTimeUnixNano}, // time_unix_nano(time) => int - "time_format": &objects.UserFunction{Name: "time_format", Value: timesTimeFormat}, // time_format(time, format) => string - "time_location": &objects.UserFunction{Name: "time_location", Value: timesTimeLocation}, // time_location(time) => string - "time_string": &objects.UserFunction{Name: "time_string", Value: timesTimeString}, // time_string(time) => string - "is_zero": &objects.UserFunction{Name: "is_zero", Value: timesIsZero}, // is_zero(time) => bool - "to_local": &objects.UserFunction{Name: "to_local", Value: timesToLocal}, // to_local(time) => time - "to_utc": &objects.UserFunction{Name: "to_utc", Value: timesToUTC}, // to_utc(time) => time +var timesModule = map[string]tengo.Object{ + "format_ansic": &tengo.String{Value: time.ANSIC}, + "format_unix_date": &tengo.String{Value: time.UnixDate}, + "format_ruby_date": &tengo.String{Value: time.RubyDate}, + "format_rfc822": &tengo.String{Value: time.RFC822}, + "format_rfc822z": &tengo.String{Value: time.RFC822Z}, + "format_rfc850": &tengo.String{Value: time.RFC850}, + "format_rfc1123": &tengo.String{Value: time.RFC1123}, + "format_rfc1123z": &tengo.String{Value: time.RFC1123Z}, + "format_rfc3339": &tengo.String{Value: time.RFC3339}, + "format_rfc3339_nano": &tengo.String{Value: time.RFC3339Nano}, + "format_kitchen": &tengo.String{Value: time.Kitchen}, + "format_stamp": &tengo.String{Value: time.Stamp}, + "format_stamp_milli": &tengo.String{Value: time.StampMilli}, + "format_stamp_micro": &tengo.String{Value: time.StampMicro}, + "format_stamp_nano": &tengo.String{Value: time.StampNano}, + "nanosecond": &tengo.Int{Value: int64(time.Nanosecond)}, + "microsecond": &tengo.Int{Value: int64(time.Microsecond)}, + "millisecond": &tengo.Int{Value: int64(time.Millisecond)}, + "second": &tengo.Int{Value: int64(time.Second)}, + "minute": &tengo.Int{Value: int64(time.Minute)}, + "hour": &tengo.Int{Value: int64(time.Hour)}, + "january": &tengo.Int{Value: int64(time.January)}, + "february": &tengo.Int{Value: int64(time.February)}, + "march": &tengo.Int{Value: int64(time.March)}, + "april": &tengo.Int{Value: int64(time.April)}, + "may": &tengo.Int{Value: int64(time.May)}, + "june": &tengo.Int{Value: int64(time.June)}, + "july": &tengo.Int{Value: int64(time.July)}, + "august": &tengo.Int{Value: int64(time.August)}, + "september": &tengo.Int{Value: int64(time.September)}, + "october": &tengo.Int{Value: int64(time.October)}, + "november": &tengo.Int{Value: int64(time.November)}, + "december": &tengo.Int{Value: int64(time.December)}, + "sleep": &tengo.UserFunction{ + Name: "sleep", + Value: timesSleep, + }, // sleep(int) + "parse_duration": &tengo.UserFunction{ + Name: "parse_duration", + Value: timesParseDuration, + }, // parse_duration(str) => int + "since": &tengo.UserFunction{ + Name: "since", + Value: timesSince, + }, // since(time) => int + "until": &tengo.UserFunction{ + Name: "until", + Value: timesUntil, + }, // until(time) => int + "duration_hours": &tengo.UserFunction{ + Name: "duration_hours", + Value: timesDurationHours, + }, // duration_hours(int) => float + "duration_minutes": &tengo.UserFunction{ + Name: "duration_minutes", + Value: timesDurationMinutes, + }, // duration_minutes(int) => float + "duration_nanoseconds": &tengo.UserFunction{ + Name: "duration_nanoseconds", + Value: timesDurationNanoseconds, + }, // duration_nanoseconds(int) => int + "duration_seconds": &tengo.UserFunction{ + Name: "duration_seconds", + Value: timesDurationSeconds, + }, // duration_seconds(int) => float + "duration_string": &tengo.UserFunction{ + Name: "duration_string", + Value: timesDurationString, + }, // duration_string(int) => string + "month_string": &tengo.UserFunction{ + Name: "month_string", + Value: timesMonthString, + }, // month_string(int) => string + "date": &tengo.UserFunction{ + Name: "date", + Value: timesDate, + }, // date(year, month, day, hour, min, sec, nsec) => time + "now": &tengo.UserFunction{ + Name: "now", + Value: timesNow, + }, // now() => time + "parse": &tengo.UserFunction{ + Name: "parse", + Value: timesParse, + }, // parse(format, str) => time + "unix": &tengo.UserFunction{ + Name: "unix", + Value: timesUnix, + }, // unix(sec, nsec) => time + "add": &tengo.UserFunction{ + Name: "add", + Value: timesAdd, + }, // add(time, int) => time + "add_date": &tengo.UserFunction{ + Name: "add_date", + Value: timesAddDate, + }, // add_date(time, years, months, days) => time + "sub": &tengo.UserFunction{ + Name: "sub", + Value: timesSub, + }, // sub(t time, u time) => int + "after": &tengo.UserFunction{ + Name: "after", + Value: timesAfter, + }, // after(t time, u time) => bool + "before": &tengo.UserFunction{ + Name: "before", + Value: timesBefore, + }, // before(t time, u time) => bool + "time_year": &tengo.UserFunction{ + Name: "time_year", + Value: timesTimeYear, + }, // time_year(time) => int + "time_month": &tengo.UserFunction{ + Name: "time_month", + Value: timesTimeMonth, + }, // time_month(time) => int + "time_day": &tengo.UserFunction{ + Name: "time_day", + Value: timesTimeDay, + }, // time_day(time) => int + "time_weekday": &tengo.UserFunction{ + Name: "time_weekday", + Value: timesTimeWeekday, + }, // time_weekday(time) => int + "time_hour": &tengo.UserFunction{ + Name: "time_hour", + Value: timesTimeHour, + }, // time_hour(time) => int + "time_minute": &tengo.UserFunction{ + Name: "time_minute", + Value: timesTimeMinute, + }, // time_minute(time) => int + "time_second": &tengo.UserFunction{ + Name: "time_second", + Value: timesTimeSecond, + }, // time_second(time) => int + "time_nanosecond": &tengo.UserFunction{ + Name: "time_nanosecond", + Value: timesTimeNanosecond, + }, // time_nanosecond(time) => int + "time_unix": &tengo.UserFunction{ + Name: "time_unix", + Value: timesTimeUnix, + }, // time_unix(time) => int + "time_unix_nano": &tengo.UserFunction{ + Name: "time_unix_nano", + Value: timesTimeUnixNano, + }, // time_unix_nano(time) => int + "time_format": &tengo.UserFunction{ + Name: "time_format", + Value: timesTimeFormat, + }, // time_format(time, format) => string + "time_location": &tengo.UserFunction{ + Name: "time_location", + Value: timesTimeLocation, + }, // time_location(time) => string + "time_string": &tengo.UserFunction{ + Name: "time_string", + Value: timesTimeString, + }, // time_string(time) => string + "is_zero": &tengo.UserFunction{ + Name: "is_zero", + Value: timesIsZero, + }, // is_zero(time) => bool + "to_local": &tengo.UserFunction{ + Name: "to_local", + Value: timesToLocal, + }, // to_local(time) => time + "to_utc": &tengo.UserFunction{ + Name: "to_utc", + Value: timesToUTC, + }, // to_utc(time) => time } -func timesSleep(args ...objects.Object) (ret objects.Object, err error) { +func timesSleep(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 1 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - i1, ok := objects.ToInt64(args[0]) + i1, ok := tengo.ToInt64(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "int(compatible)", Found: args[0].TypeName(), @@ -95,20 +199,23 @@ func timesSleep(args ...objects.Object) (ret objects.Object, err error) { } time.Sleep(time.Duration(i1)) - ret = objects.UndefinedValue + ret = tengo.UndefinedValue return } -func timesParseDuration(args ...objects.Object) (ret objects.Object, err error) { +func timesParseDuration(args ...tengo.Object) ( + ret tengo.Object, + err error, +) { if len(args) != 1 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - s1, ok := objects.ToString(args[0]) + s1, ok := tengo.ToString(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), @@ -122,20 +229,23 @@ func timesParseDuration(args ...objects.Object) (ret objects.Object, err error) return } - ret = &objects.Int{Value: int64(dur)} + ret = &tengo.Int{Value: int64(dur)} return } -func timesSince(args ...objects.Object) (ret objects.Object, err error) { +func timesSince(args ...tengo.Object) ( + ret tengo.Object, + err error, +) { if len(args) != 1 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - t1, ok := objects.ToTime(args[0]) + t1, ok := tengo.ToTime(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "time(compatible)", Found: args[0].TypeName(), @@ -143,20 +253,23 @@ func timesSince(args ...objects.Object) (ret objects.Object, err error) { return } - ret = &objects.Int{Value: int64(time.Since(t1))} + ret = &tengo.Int{Value: int64(time.Since(t1))} return } -func timesUntil(args ...objects.Object) (ret objects.Object, err error) { +func timesUntil(args ...tengo.Object) ( + ret tengo.Object, + err error, +) { if len(args) != 1 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - t1, ok := objects.ToTime(args[0]) + t1, ok := tengo.ToTime(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "time(compatible)", Found: args[0].TypeName(), @@ -164,20 +277,23 @@ func timesUntil(args ...objects.Object) (ret objects.Object, err error) { return } - ret = &objects.Int{Value: int64(time.Until(t1))} + ret = &tengo.Int{Value: int64(time.Until(t1))} return } -func timesDurationHours(args ...objects.Object) (ret objects.Object, err error) { +func timesDurationHours(args ...tengo.Object) ( + ret tengo.Object, + err error, +) { if len(args) != 1 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - i1, ok := objects.ToInt64(args[0]) + i1, ok := tengo.ToInt64(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "int(compatible)", Found: args[0].TypeName(), @@ -185,20 +301,23 @@ func timesDurationHours(args ...objects.Object) (ret objects.Object, err error) return } - ret = &objects.Float{Value: time.Duration(i1).Hours()} + ret = &tengo.Float{Value: time.Duration(i1).Hours()} return } -func timesDurationMinutes(args ...objects.Object) (ret objects.Object, err error) { +func timesDurationMinutes(args ...tengo.Object) ( + ret tengo.Object, + err error, +) { if len(args) != 1 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - i1, ok := objects.ToInt64(args[0]) + i1, ok := tengo.ToInt64(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "int(compatible)", Found: args[0].TypeName(), @@ -206,20 +325,23 @@ func timesDurationMinutes(args ...objects.Object) (ret objects.Object, err error return } - ret = &objects.Float{Value: time.Duration(i1).Minutes()} + ret = &tengo.Float{Value: time.Duration(i1).Minutes()} return } -func timesDurationNanoseconds(args ...objects.Object) (ret objects.Object, err error) { +func timesDurationNanoseconds(args ...tengo.Object) ( + ret tengo.Object, + err error, +) { if len(args) != 1 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - i1, ok := objects.ToInt64(args[0]) + i1, ok := tengo.ToInt64(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "int(compatible)", Found: args[0].TypeName(), @@ -227,20 +349,23 @@ func timesDurationNanoseconds(args ...objects.Object) (ret objects.Object, err e return } - ret = &objects.Int{Value: time.Duration(i1).Nanoseconds()} + ret = &tengo.Int{Value: time.Duration(i1).Nanoseconds()} return } -func timesDurationSeconds(args ...objects.Object) (ret objects.Object, err error) { +func timesDurationSeconds(args ...tengo.Object) ( + ret tengo.Object, + err error, +) { if len(args) != 1 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - i1, ok := objects.ToInt64(args[0]) + i1, ok := tengo.ToInt64(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "int(compatible)", Found: args[0].TypeName(), @@ -248,20 +373,23 @@ func timesDurationSeconds(args ...objects.Object) (ret objects.Object, err error return } - ret = &objects.Float{Value: time.Duration(i1).Seconds()} + ret = &tengo.Float{Value: time.Duration(i1).Seconds()} return } -func timesDurationString(args ...objects.Object) (ret objects.Object, err error) { +func timesDurationString(args ...tengo.Object) ( + ret tengo.Object, + err error, +) { if len(args) != 1 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - i1, ok := objects.ToInt64(args[0]) + i1, ok := tengo.ToInt64(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "int(compatible)", Found: args[0].TypeName(), @@ -269,20 +397,23 @@ func timesDurationString(args ...objects.Object) (ret objects.Object, err error) return } - ret = &objects.String{Value: time.Duration(i1).String()} + ret = &tengo.String{Value: time.Duration(i1).String()} return } -func timesMonthString(args ...objects.Object) (ret objects.Object, err error) { +func timesMonthString(args ...tengo.Object) ( + ret tengo.Object, + err error, +) { if len(args) != 1 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - i1, ok := objects.ToInt64(args[0]) + i1, ok := tengo.ToInt64(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "int(compatible)", Found: args[0].TypeName(), @@ -290,74 +421,77 @@ func timesMonthString(args ...objects.Object) (ret objects.Object, err error) { return } - ret = &objects.String{Value: time.Month(i1).String()} + ret = &tengo.String{Value: time.Month(i1).String()} return } -func timesDate(args ...objects.Object) (ret objects.Object, err error) { +func timesDate(args ...tengo.Object) ( + ret tengo.Object, + err error, +) { if len(args) != 7 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - i1, ok := objects.ToInt(args[0]) + i1, ok := tengo.ToInt(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "int(compatible)", Found: args[0].TypeName(), } return } - i2, ok := objects.ToInt(args[1]) + i2, ok := tengo.ToInt(args[1]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "second", Expected: "int(compatible)", Found: args[1].TypeName(), } return } - i3, ok := objects.ToInt(args[2]) + i3, ok := tengo.ToInt(args[2]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "third", Expected: "int(compatible)", Found: args[2].TypeName(), } return } - i4, ok := objects.ToInt(args[3]) + i4, ok := tengo.ToInt(args[3]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "fourth", Expected: "int(compatible)", Found: args[3].TypeName(), } return } - i5, ok := objects.ToInt(args[4]) + i5, ok := tengo.ToInt(args[4]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "fifth", Expected: "int(compatible)", Found: args[4].TypeName(), } return } - i6, ok := objects.ToInt(args[5]) + i6, ok := tengo.ToInt(args[5]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "sixth", Expected: "int(compatible)", Found: args[5].TypeName(), } return } - i7, ok := objects.ToInt(args[6]) + i7, ok := tengo.ToInt(args[6]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "seventh", Expected: "int(compatible)", Found: args[6].TypeName(), @@ -365,31 +499,34 @@ func timesDate(args ...objects.Object) (ret objects.Object, err error) { return } - ret = &objects.Time{Value: time.Date(i1, time.Month(i2), i3, i4, i5, i6, i7, time.Now().Location())} + ret = &tengo.Time{ + Value: time.Date(i1, + time.Month(i2), i3, i4, i5, i6, i7, time.Now().Location()), + } return } -func timesNow(args ...objects.Object) (ret objects.Object, err error) { +func timesNow(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 0 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - ret = &objects.Time{Value: time.Now()} + ret = &tengo.Time{Value: time.Now()} return } -func timesParse(args ...objects.Object) (ret objects.Object, err error) { +func timesParse(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 2 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - s1, ok := objects.ToString(args[0]) + s1, ok := tengo.ToString(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "string(compatible)", Found: args[0].TypeName(), @@ -397,9 +534,9 @@ func timesParse(args ...objects.Object) (ret objects.Object, err error) { return } - s2, ok := objects.ToString(args[1]) + s2, ok := tengo.ToString(args[1]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "second", Expected: "string(compatible)", Found: args[1].TypeName(), @@ -413,20 +550,20 @@ func timesParse(args ...objects.Object) (ret objects.Object, err error) { return } - ret = &objects.Time{Value: parsed} + ret = &tengo.Time{Value: parsed} return } -func timesUnix(args ...objects.Object) (ret objects.Object, err error) { +func timesUnix(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 2 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - i1, ok := objects.ToInt64(args[0]) + i1, ok := tengo.ToInt64(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "int(compatible)", Found: args[0].TypeName(), @@ -434,9 +571,9 @@ func timesUnix(args ...objects.Object) (ret objects.Object, err error) { return } - i2, ok := objects.ToInt64(args[1]) + i2, ok := tengo.ToInt64(args[1]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "second", Expected: "int(compatible)", Found: args[1].TypeName(), @@ -444,20 +581,20 @@ func timesUnix(args ...objects.Object) (ret objects.Object, err error) { return } - ret = &objects.Time{Value: time.Unix(i1, i2)} + ret = &tengo.Time{Value: time.Unix(i1, i2)} return } -func timesAdd(args ...objects.Object) (ret objects.Object, err error) { +func timesAdd(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 2 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - t1, ok := objects.ToTime(args[0]) + t1, ok := tengo.ToTime(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "time(compatible)", Found: args[0].TypeName(), @@ -465,9 +602,9 @@ func timesAdd(args ...objects.Object) (ret objects.Object, err error) { return } - i2, ok := objects.ToInt64(args[1]) + i2, ok := tengo.ToInt64(args[1]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "second", Expected: "int(compatible)", Found: args[1].TypeName(), @@ -475,20 +612,20 @@ func timesAdd(args ...objects.Object) (ret objects.Object, err error) { return } - ret = &objects.Time{Value: t1.Add(time.Duration(i2))} + ret = &tengo.Time{Value: t1.Add(time.Duration(i2))} return } -func timesSub(args ...objects.Object) (ret objects.Object, err error) { +func timesSub(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 2 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - t1, ok := objects.ToTime(args[0]) + t1, ok := tengo.ToTime(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "time(compatible)", Found: args[0].TypeName(), @@ -496,9 +633,9 @@ func timesSub(args ...objects.Object) (ret objects.Object, err error) { return } - t2, ok := objects.ToTime(args[1]) + t2, ok := tengo.ToTime(args[1]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "second", Expected: "time(compatible)", Found: args[1].TypeName(), @@ -506,20 +643,20 @@ func timesSub(args ...objects.Object) (ret objects.Object, err error) { return } - ret = &objects.Int{Value: int64(t1.Sub(t2))} + ret = &tengo.Int{Value: int64(t1.Sub(t2))} return } -func timesAddDate(args ...objects.Object) (ret objects.Object, err error) { +func timesAddDate(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 4 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - t1, ok := objects.ToTime(args[0]) + t1, ok := tengo.ToTime(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "time(compatible)", Found: args[0].TypeName(), @@ -527,9 +664,9 @@ func timesAddDate(args ...objects.Object) (ret objects.Object, err error) { return } - i2, ok := objects.ToInt(args[1]) + i2, ok := tengo.ToInt(args[1]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "second", Expected: "int(compatible)", Found: args[1].TypeName(), @@ -537,9 +674,9 @@ func timesAddDate(args ...objects.Object) (ret objects.Object, err error) { return } - i3, ok := objects.ToInt(args[2]) + i3, ok := tengo.ToInt(args[2]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "third", Expected: "int(compatible)", Found: args[2].TypeName(), @@ -547,9 +684,9 @@ func timesAddDate(args ...objects.Object) (ret objects.Object, err error) { return } - i4, ok := objects.ToInt(args[3]) + i4, ok := tengo.ToInt(args[3]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "fourth", Expected: "int(compatible)", Found: args[3].TypeName(), @@ -557,20 +694,20 @@ func timesAddDate(args ...objects.Object) (ret objects.Object, err error) { return } - ret = &objects.Time{Value: t1.AddDate(i2, i3, i4)} + ret = &tengo.Time{Value: t1.AddDate(i2, i3, i4)} return } -func timesAfter(args ...objects.Object) (ret objects.Object, err error) { +func timesAfter(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 2 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - t1, ok := objects.ToTime(args[0]) + t1, ok := tengo.ToTime(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "time(compatible)", Found: args[0].TypeName(), @@ -578,9 +715,9 @@ func timesAfter(args ...objects.Object) (ret objects.Object, err error) { return } - t2, ok := objects.ToTime(args[1]) + t2, ok := tengo.ToTime(args[1]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "second", Expected: "time(compatible)", Found: args[1].TypeName(), @@ -589,23 +726,23 @@ func timesAfter(args ...objects.Object) (ret objects.Object, err error) { } if t1.After(t2) { - ret = objects.TrueValue + ret = tengo.TrueValue } else { - ret = objects.FalseValue + ret = tengo.FalseValue } return } -func timesBefore(args ...objects.Object) (ret objects.Object, err error) { +func timesBefore(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 2 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - t1, ok := objects.ToTime(args[0]) + t1, ok := tengo.ToTime(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "time(compatible)", Found: args[0].TypeName(), @@ -613,9 +750,9 @@ func timesBefore(args ...objects.Object) (ret objects.Object, err error) { return } - t2, ok := objects.ToTime(args[1]) + t2, ok := tengo.ToTime(args[1]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "second", Expected: "time(compatible)", Found: args[0].TypeName(), @@ -624,23 +761,23 @@ func timesBefore(args ...objects.Object) (ret objects.Object, err error) { } if t1.Before(t2) { - ret = objects.TrueValue + ret = tengo.TrueValue } else { - ret = objects.FalseValue + ret = tengo.FalseValue } return } -func timesTimeYear(args ...objects.Object) (ret objects.Object, err error) { +func timesTimeYear(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 1 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - t1, ok := objects.ToTime(args[0]) + t1, ok := tengo.ToTime(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "time(compatible)", Found: args[0].TypeName(), @@ -648,20 +785,20 @@ func timesTimeYear(args ...objects.Object) (ret objects.Object, err error) { return } - ret = &objects.Int{Value: int64(t1.Year())} + ret = &tengo.Int{Value: int64(t1.Year())} return } -func timesTimeMonth(args ...objects.Object) (ret objects.Object, err error) { +func timesTimeMonth(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 1 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - t1, ok := objects.ToTime(args[0]) + t1, ok := tengo.ToTime(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "time(compatible)", Found: args[0].TypeName(), @@ -669,20 +806,20 @@ func timesTimeMonth(args ...objects.Object) (ret objects.Object, err error) { return } - ret = &objects.Int{Value: int64(t1.Month())} + ret = &tengo.Int{Value: int64(t1.Month())} return } -func timesTimeDay(args ...objects.Object) (ret objects.Object, err error) { +func timesTimeDay(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 1 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - t1, ok := objects.ToTime(args[0]) + t1, ok := tengo.ToTime(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "time(compatible)", Found: args[0].TypeName(), @@ -690,20 +827,20 @@ func timesTimeDay(args ...objects.Object) (ret objects.Object, err error) { return } - ret = &objects.Int{Value: int64(t1.Day())} + ret = &tengo.Int{Value: int64(t1.Day())} return } -func timesTimeWeekday(args ...objects.Object) (ret objects.Object, err error) { +func timesTimeWeekday(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 1 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - t1, ok := objects.ToTime(args[0]) + t1, ok := tengo.ToTime(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "time(compatible)", Found: args[0].TypeName(), @@ -711,20 +848,20 @@ func timesTimeWeekday(args ...objects.Object) (ret objects.Object, err error) { return } - ret = &objects.Int{Value: int64(t1.Weekday())} + ret = &tengo.Int{Value: int64(t1.Weekday())} return } -func timesTimeHour(args ...objects.Object) (ret objects.Object, err error) { +func timesTimeHour(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 1 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - t1, ok := objects.ToTime(args[0]) + t1, ok := tengo.ToTime(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "time(compatible)", Found: args[0].TypeName(), @@ -732,20 +869,20 @@ func timesTimeHour(args ...objects.Object) (ret objects.Object, err error) { return } - ret = &objects.Int{Value: int64(t1.Hour())} + ret = &tengo.Int{Value: int64(t1.Hour())} return } -func timesTimeMinute(args ...objects.Object) (ret objects.Object, err error) { +func timesTimeMinute(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 1 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - t1, ok := objects.ToTime(args[0]) + t1, ok := tengo.ToTime(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "time(compatible)", Found: args[0].TypeName(), @@ -753,20 +890,20 @@ func timesTimeMinute(args ...objects.Object) (ret objects.Object, err error) { return } - ret = &objects.Int{Value: int64(t1.Minute())} + ret = &tengo.Int{Value: int64(t1.Minute())} return } -func timesTimeSecond(args ...objects.Object) (ret objects.Object, err error) { +func timesTimeSecond(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 1 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - t1, ok := objects.ToTime(args[0]) + t1, ok := tengo.ToTime(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "time(compatible)", Found: args[0].TypeName(), @@ -774,20 +911,23 @@ func timesTimeSecond(args ...objects.Object) (ret objects.Object, err error) { return } - ret = &objects.Int{Value: int64(t1.Second())} + ret = &tengo.Int{Value: int64(t1.Second())} return } -func timesTimeNanosecond(args ...objects.Object) (ret objects.Object, err error) { +func timesTimeNanosecond(args ...tengo.Object) ( + ret tengo.Object, + err error, +) { if len(args) != 1 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - t1, ok := objects.ToTime(args[0]) + t1, ok := tengo.ToTime(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "time(compatible)", Found: args[0].TypeName(), @@ -795,20 +935,20 @@ func timesTimeNanosecond(args ...objects.Object) (ret objects.Object, err error) return } - ret = &objects.Int{Value: int64(t1.Nanosecond())} + ret = &tengo.Int{Value: int64(t1.Nanosecond())} return } -func timesTimeUnix(args ...objects.Object) (ret objects.Object, err error) { +func timesTimeUnix(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 1 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - t1, ok := objects.ToTime(args[0]) + t1, ok := tengo.ToTime(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "time(compatible)", Found: args[0].TypeName(), @@ -816,20 +956,23 @@ func timesTimeUnix(args ...objects.Object) (ret objects.Object, err error) { return } - ret = &objects.Int{Value: int64(t1.Unix())} + ret = &tengo.Int{Value: t1.Unix()} return } -func timesTimeUnixNano(args ...objects.Object) (ret objects.Object, err error) { +func timesTimeUnixNano(args ...tengo.Object) ( + ret tengo.Object, + err error, +) { if len(args) != 1 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - t1, ok := objects.ToTime(args[0]) + t1, ok := tengo.ToTime(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "time(compatible)", Found: args[0].TypeName(), @@ -837,20 +980,20 @@ func timesTimeUnixNano(args ...objects.Object) (ret objects.Object, err error) { return } - ret = &objects.Int{Value: int64(t1.UnixNano())} + ret = &tengo.Int{Value: t1.UnixNano()} return } -func timesTimeFormat(args ...objects.Object) (ret objects.Object, err error) { +func timesTimeFormat(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 2 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - t1, ok := objects.ToTime(args[0]) + t1, ok := tengo.ToTime(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "time(compatible)", Found: args[0].TypeName(), @@ -858,9 +1001,9 @@ func timesTimeFormat(args ...objects.Object) (ret objects.Object, err error) { return } - s2, ok := objects.ToString(args[1]) + s2, ok := tengo.ToString(args[1]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "second", Expected: "string(compatible)", Found: args[1].TypeName(), @@ -871,23 +1014,23 @@ func timesTimeFormat(args ...objects.Object) (ret objects.Object, err error) { s := t1.Format(s2) if len(s) > tengo.MaxStringLen { - return nil, objects.ErrStringLimit + return nil, tengo.ErrStringLimit } - ret = &objects.String{Value: s} + ret = &tengo.String{Value: s} return } -func timesIsZero(args ...objects.Object) (ret objects.Object, err error) { +func timesIsZero(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 1 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - t1, ok := objects.ToTime(args[0]) + t1, ok := tengo.ToTime(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "time(compatible)", Found: args[0].TypeName(), @@ -896,23 +1039,23 @@ func timesIsZero(args ...objects.Object) (ret objects.Object, err error) { } if t1.IsZero() { - ret = objects.TrueValue + ret = tengo.TrueValue } else { - ret = objects.FalseValue + ret = tengo.FalseValue } return } -func timesToLocal(args ...objects.Object) (ret objects.Object, err error) { +func timesToLocal(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 1 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - t1, ok := objects.ToTime(args[0]) + t1, ok := tengo.ToTime(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "time(compatible)", Found: args[0].TypeName(), @@ -920,20 +1063,20 @@ func timesToLocal(args ...objects.Object) (ret objects.Object, err error) { return } - ret = &objects.Time{Value: t1.Local()} + ret = &tengo.Time{Value: t1.Local()} return } -func timesToUTC(args ...objects.Object) (ret objects.Object, err error) { +func timesToUTC(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 1 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - t1, ok := objects.ToTime(args[0]) + t1, ok := tengo.ToTime(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "time(compatible)", Found: args[0].TypeName(), @@ -941,20 +1084,23 @@ func timesToUTC(args ...objects.Object) (ret objects.Object, err error) { return } - ret = &objects.Time{Value: t1.UTC()} + ret = &tengo.Time{Value: t1.UTC()} return } -func timesTimeLocation(args ...objects.Object) (ret objects.Object, err error) { +func timesTimeLocation(args ...tengo.Object) ( + ret tengo.Object, + err error, +) { if len(args) != 1 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - t1, ok := objects.ToTime(args[0]) + t1, ok := tengo.ToTime(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "time(compatible)", Found: args[0].TypeName(), @@ -962,20 +1108,20 @@ func timesTimeLocation(args ...objects.Object) (ret objects.Object, err error) { return } - ret = &objects.String{Value: t1.Location().String()} + ret = &tengo.String{Value: t1.Location().String()} return } -func timesTimeString(args ...objects.Object) (ret objects.Object, err error) { +func timesTimeString(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 1 { - err = objects.ErrWrongNumArguments + err = tengo.ErrWrongNumArguments return } - t1, ok := objects.ToTime(args[0]) + t1, ok := tengo.ToTime(args[0]) if !ok { - err = objects.ErrInvalidArgumentType{ + err = tengo.ErrInvalidArgumentType{ Name: "first", Expected: "time(compatible)", Found: args[0].TypeName(), @@ -983,7 +1129,7 @@ func timesTimeString(args ...objects.Object) (ret objects.Object, err error) { return } - ret = &objects.String{Value: t1.String()} + ret = &tengo.String{Value: t1.String()} return } diff --git a/stdlib/times_test.go b/stdlib/times_test.go index a1f7579..3565c85 100644 --- a/stdlib/times_test.go +++ b/stdlib/times_test.go @@ -4,18 +4,22 @@ import ( "testing" "time" - "github.com/d5/tengo/assert" - "github.com/d5/tengo/objects" + "github.com/d5/tengo" + "github.com/d5/tengo/internal/require" ) func TestTimes(t *testing.T) { time1 := time.Date(1982, 9, 28, 19, 21, 44, 999, time.Now().Location()) time2 := time.Now() - module(t, "times").call("sleep", 1).expect(objects.UndefinedValue) + module(t, "times").call("sleep", 1).expect(tengo.UndefinedValue) - assert.True(t, module(t, "times").call("since", time.Now().Add(-time.Hour)).o.(*objects.Int).Value > 3600000000000) - assert.True(t, module(t, "times").call("until", time.Now().Add(time.Hour)).o.(*objects.Int).Value < 3600000000000) + require.True(t, module(t, "times"). + call("since", time.Now().Add(-time.Hour)). + o.(*tengo.Int).Value > 3600000000000) + require.True(t, module(t, "times"). + call("until", time.Now().Add(time.Hour)). + o.(*tengo.Int).Value < 3600000000000) module(t, "times").call("parse_duration", "1ns").expect(1) module(t, "times").call("parse_duration", "1ms").expect(1000000) @@ -29,20 +33,33 @@ func TestTimes(t *testing.T) { module(t, "times").call("month_string", 1).expect("January") module(t, "times").call("month_string", 12).expect("December") - module(t, "times").call("date", 1982, 9, 28, 19, 21, 44, 999).expect(time1) - nowD := time.Until(module(t, "times").call("now").o.(*objects.Time).Value).Nanoseconds() - assert.True(t, 0 > nowD && nowD > -100000000) // within 100ms + module(t, "times").call("date", 1982, 9, 28, 19, 21, 44, 999). + expect(time1) + nowD := time.Until(module(t, "times").call("now"). + o.(*tengo.Time).Value).Nanoseconds() + require.True(t, 0 > nowD && nowD > -100000000) // within 100ms parsed, _ := time.Parse(time.RFC3339, "1982-09-28T19:21:44+07:00") - module(t, "times").call("parse", time.RFC3339, "1982-09-28T19:21:44+07:00").expect(parsed) - module(t, "times").call("unix", 1234325, 94493).expect(time.Unix(1234325, 94493)) + module(t, "times"). + call("parse", time.RFC3339, "1982-09-28T19:21:44+07:00"). + expect(parsed) + module(t, "times"). + call("unix", 1234325, 94493). + expect(time.Unix(1234325, 94493)) - module(t, "times").call("add", time2, 3600000000000).expect(time2.Add(time.Duration(3600000000000))) - module(t, "times").call("sub", time2, time2.Add(-time.Hour)).expect(3600000000000) - module(t, "times").call("add_date", time2, 1, 2, 3).expect(time2.AddDate(1, 2, 3)) - module(t, "times").call("after", time2, time2.Add(time.Hour)).expect(false) - module(t, "times").call("after", time2, time2.Add(-time.Hour)).expect(true) - module(t, "times").call("before", time2, time2.Add(time.Hour)).expect(true) - module(t, "times").call("before", time2, time2.Add(-time.Hour)).expect(false) + module(t, "times").call("add", time2, 3600000000000). + expect(time2.Add(time.Duration(3600000000000))) + module(t, "times").call("sub", time2, time2.Add(-time.Hour)). + expect(3600000000000) + module(t, "times").call("add_date", time2, 1, 2, 3). + expect(time2.AddDate(1, 2, 3)) + module(t, "times").call("after", time2, time2.Add(time.Hour)). + expect(false) + module(t, "times").call("after", time2, time2.Add(-time.Hour)). + expect(true) + module(t, "times").call("before", time2, time2.Add(time.Hour)). + expect(true) + module(t, "times").call("before", time2, time2.Add(-time.Hour)). + expect(false) module(t, "times").call("time_year", time1).expect(time1.Year()) module(t, "times").call("time_month", time1).expect(int(time1.Month())) @@ -50,14 +67,17 @@ func TestTimes(t *testing.T) { module(t, "times").call("time_hour", time1).expect(time1.Hour()) module(t, "times").call("time_minute", time1).expect(time1.Minute()) module(t, "times").call("time_second", time1).expect(time1.Second()) - module(t, "times").call("time_nanosecond", time1).expect(time1.Nanosecond()) + module(t, "times").call("time_nanosecond", time1). + expect(time1.Nanosecond()) module(t, "times").call("time_unix", time1).expect(time1.Unix()) module(t, "times").call("time_unix_nano", time1).expect(time1.UnixNano()) - module(t, "times").call("time_format", time1, time.RFC3339).expect(time1.Format(time.RFC3339)) + module(t, "times").call("time_format", time1, time.RFC3339). + expect(time1.Format(time.RFC3339)) module(t, "times").call("is_zero", time1).expect(false) module(t, "times").call("is_zero", time.Time{}).expect(true) module(t, "times").call("to_local", time1).expect(time1.Local()) module(t, "times").call("to_utc", time1).expect(time1.UTC()) - module(t, "times").call("time_location", time1).expect(time1.Location().String()) + module(t, "times").call("time_location", time1). + expect(time1.Location().String()) module(t, "times").call("time_string", time1).expect(time1.String()) } diff --git a/tengo.go b/tengo.go index a883bbd..098a197 100644 --- a/tengo.go +++ b/tengo.go @@ -1,11 +1,306 @@ package tengo +import ( + "errors" + "fmt" + "strconv" + "time" +) + var ( - // MaxStringLen is the maximum byte-length for string value. - // Note this limit applies to all compiler/VM instances in the process. + // MaxStringLen is the maximum byte-length for string value. Note this + // limit applies to all compiler/VM instances in the process. MaxStringLen = 2147483647 - // MaxBytesLen is the maximum length for bytes value. - // Note this limit applies to all compiler/VM instances in the process. + // MaxBytesLen is the maximum length for bytes value. Note this limit + // applies to all compiler/VM instances in the process. MaxBytesLen = 2147483647 ) + +const ( + // GlobalsSize is the maximum number of global variables for a VM. + GlobalsSize = 1024 + + // StackSize is the maximum stack size for a VM. + StackSize = 2048 + + // MaxFrames is the maximum number of function frames for a VM. + MaxFrames = 1024 +) + +// CallableFunc is a function signature for the callable functions. +type CallableFunc = func(args ...Object) (ret Object, err error) + +// CountObjects returns the number of objects that a given object o contains. +// For scalar value types, it will always be 1. For compound value types, +// this will include its elements and all of their elements recursively. +func CountObjects(o Object) (c int) { + c = 1 + switch o := o.(type) { + case *Array: + for _, v := range o.Value { + c += CountObjects(v) + } + case *ImmutableArray: + for _, v := range o.Value { + c += CountObjects(v) + } + case *Map: + for _, v := range o.Value { + c += CountObjects(v) + } + case *ImmutableMap: + for _, v := range o.Value { + c += CountObjects(v) + } + case *Error: + c += CountObjects(o.Value) + } + return +} + +// ToString will try to convert object o to string value. +func ToString(o Object) (v string, ok bool) { + if o == UndefinedValue { + return + } + ok = true + if str, isStr := o.(*String); isStr { + v = str.Value + } else { + v = o.String() + } + return +} + +// ToInt will try to convert object o to int value. +func ToInt(o Object) (v int, ok bool) { + switch o := o.(type) { + case *Int: + v = int(o.Value) + ok = true + case *Float: + v = int(o.Value) + ok = true + case *Char: + v = int(o.Value) + ok = true + case *Bool: + if o == TrueValue { + v = 1 + } + ok = true + case *String: + c, err := strconv.ParseInt(o.Value, 10, 64) + if err == nil { + v = int(c) + ok = true + } + } + return +} + +// ToInt64 will try to convert object o to int64 value. +func ToInt64(o Object) (v int64, ok bool) { + switch o := o.(type) { + case *Int: + v = o.Value + ok = true + case *Float: + v = int64(o.Value) + ok = true + case *Char: + v = int64(o.Value) + ok = true + case *Bool: + if o == TrueValue { + v = 1 + } + ok = true + case *String: + c, err := strconv.ParseInt(o.Value, 10, 64) + if err == nil { + v = c + ok = true + } + } + return +} + +// ToFloat64 will try to convert object o to float64 value. +func ToFloat64(o Object) (v float64, ok bool) { + switch o := o.(type) { + case *Int: + v = float64(o.Value) + ok = true + case *Float: + v = o.Value + ok = true + case *String: + c, err := strconv.ParseFloat(o.Value, 64) + if err == nil { + v = c + ok = true + } + } + return +} + +// ToBool will try to convert object o to bool value. +func ToBool(o Object) (v bool, ok bool) { + ok = true + v = !o.IsFalsy() + return +} + +// ToRune will try to convert object o to rune value. +func ToRune(o Object) (v rune, ok bool) { + switch o := o.(type) { + case *Int: + v = rune(o.Value) + ok = true + case *Char: + v = o.Value + ok = true + } + return +} + +// ToByteSlice will try to convert object o to []byte value. +func ToByteSlice(o Object) (v []byte, ok bool) { + switch o := o.(type) { + case *Bytes: + v = o.Value + ok = true + case *String: + v = []byte(o.Value) + ok = true + } + return +} + +// ToTime will try to convert object o to time.Time value. +func ToTime(o Object) (v time.Time, ok bool) { + switch o := o.(type) { + case *Time: + v = o.Value + ok = true + case *Int: + v = time.Unix(o.Value, 0) + ok = true + } + return +} + +// ToInterface attempts to convert an object o to an interface{} value +func ToInterface(o Object) (res interface{}) { + switch o := o.(type) { + case *Int: + res = o.Value + case *String: + res = o.Value + case *Float: + res = o.Value + case *Bool: + res = o == TrueValue + case *Char: + res = o.Value + case *Bytes: + res = o.Value + case *Array: + res = make([]interface{}, len(o.Value)) + for i, val := range o.Value { + res.([]interface{})[i] = ToInterface(val) + } + case *ImmutableArray: + res = make([]interface{}, len(o.Value)) + for i, val := range o.Value { + res.([]interface{})[i] = ToInterface(val) + } + case *Map: + res = make(map[string]interface{}) + for key, v := range o.Value { + res.(map[string]interface{})[key] = ToInterface(v) + } + case *ImmutableMap: + res = make(map[string]interface{}) + for key, v := range o.Value { + res.(map[string]interface{})[key] = ToInterface(v) + } + case *Time: + res = o.Value + case *Error: + res = errors.New(o.String()) + case *Undefined: + res = nil + case Object: + return o + } + return +} + +// FromInterface will attempt to convert an interface{} v to a Tengo Object +func FromInterface(v interface{}) (Object, error) { + switch v := v.(type) { + case nil: + return UndefinedValue, nil + case string: + if len(v) > MaxStringLen { + return nil, ErrStringLimit + } + return &String{Value: v}, nil + case int64: + return &Int{Value: v}, nil + case int: + return &Int{Value: int64(v)}, nil + case bool: + if v { + return TrueValue, nil + } + return FalseValue, nil + case rune: + return &Char{Value: v}, nil + case byte: + return &Char{Value: rune(v)}, nil + case float64: + return &Float{Value: v}, nil + case []byte: + if len(v) > MaxBytesLen { + return nil, ErrBytesLimit + } + return &Bytes{Value: v}, nil + case error: + return &Error{Value: &String{Value: v.Error()}}, nil + case map[string]Object: + return &Map{Value: v}, nil + case map[string]interface{}: + kv := make(map[string]Object) + for vk, vv := range v { + vo, err := FromInterface(vv) + if err != nil { + return nil, err + } + kv[vk] = vo + } + return &Map{Value: kv}, nil + case []Object: + return &Array{Value: v}, nil + case []interface{}: + arr := make([]Object, len(v)) + for i, e := range v { + vo, err := FromInterface(e) + if err != nil { + return nil, err + } + arr[i] = vo + } + return &Array{Value: arr}, nil + case time.Time: + return &Time{Value: v}, nil + case Object: + return v, nil + case CallableFunc: + return &UserFunction{Value: v}, nil + } + return nil, fmt.Errorf("cannot convert to object: %T", v) +} diff --git a/tengo_test.go b/tengo_test.go new file mode 100644 index 0000000..825d986 --- /dev/null +++ b/tengo_test.go @@ -0,0 +1,137 @@ +package tengo_test + +import ( + "strings" + "testing" + "time" + + "github.com/d5/tengo" + "github.com/d5/tengo/internal" + "github.com/d5/tengo/internal/require" +) + +func TestInstructions_String(t *testing.T) { + assertInstructionString(t, + [][]byte{ + internal.MakeInstruction(internal.OpConstant, 1), + internal.MakeInstruction(internal.OpConstant, 2), + internal.MakeInstruction(internal.OpConstant, 65535), + }, + `0000 CONST 1 +0003 CONST 2 +0006 CONST 65535`) + + assertInstructionString(t, + [][]byte{ + internal.MakeInstruction(internal.OpBinaryOp, 11), + internal.MakeInstruction(internal.OpConstant, 2), + internal.MakeInstruction(internal.OpConstant, 65535), + }, + `0000 BINARYOP 11 +0002 CONST 2 +0005 CONST 65535`) + + assertInstructionString(t, + [][]byte{ + internal.MakeInstruction(internal.OpBinaryOp, 11), + internal.MakeInstruction(internal.OpGetLocal, 1), + internal.MakeInstruction(internal.OpConstant, 2), + internal.MakeInstruction(internal.OpConstant, 65535), + }, + `0000 BINARYOP 11 +0002 GETL 1 +0004 CONST 2 +0007 CONST 65535`) +} + +func TestMakeInstruction(t *testing.T) { + makeInstruction(t, []byte{internal.OpConstant, 0, 0}, + internal.OpConstant, 0) + makeInstruction(t, []byte{internal.OpConstant, 0, 1}, + internal.OpConstant, 1) + makeInstruction(t, []byte{internal.OpConstant, 255, 254}, + internal.OpConstant, 65534) + makeInstruction(t, []byte{internal.OpPop}, internal.OpPop) + makeInstruction(t, []byte{internal.OpTrue}, internal.OpTrue) + makeInstruction(t, []byte{internal.OpFalse}, internal.OpFalse) +} + +func TestNumObjects(t *testing.T) { + testCountObjects(t, &tengo.Array{}, 1) + testCountObjects(t, &tengo.Array{Value: []tengo.Object{ + &tengo.Int{Value: 1}, + &tengo.Int{Value: 2}, + &tengo.Array{Value: []tengo.Object{ + &tengo.Int{Value: 3}, + &tengo.Int{Value: 4}, + &tengo.Int{Value: 5}, + }}, + }}, 7) + testCountObjects(t, tengo.TrueValue, 1) + testCountObjects(t, tengo.FalseValue, 1) + testCountObjects(t, &tengo.BuiltinFunction{}, 1) + testCountObjects(t, &tengo.Bytes{Value: []byte("foobar")}, 1) + testCountObjects(t, &tengo.Char{Value: '가'}, 1) + testCountObjects(t, &tengo.CompiledFunction{}, 1) + testCountObjects(t, &tengo.Error{Value: &tengo.Int{Value: 5}}, 2) + testCountObjects(t, &tengo.Float{Value: 19.84}, 1) + testCountObjects(t, &tengo.ImmutableArray{Value: []tengo.Object{ + &tengo.Int{Value: 1}, + &tengo.Int{Value: 2}, + &tengo.ImmutableArray{Value: []tengo.Object{ + &tengo.Int{Value: 3}, + &tengo.Int{Value: 4}, + &tengo.Int{Value: 5}, + }}, + }}, 7) + testCountObjects(t, &tengo.ImmutableMap{ + Value: map[string]tengo.Object{ + "k1": &tengo.Int{Value: 1}, + "k2": &tengo.Int{Value: 2}, + "k3": &tengo.Array{Value: []tengo.Object{ + &tengo.Int{Value: 3}, + &tengo.Int{Value: 4}, + &tengo.Int{Value: 5}, + }}, + }}, 7) + testCountObjects(t, &tengo.Int{Value: 1984}, 1) + testCountObjects(t, &tengo.Map{Value: map[string]tengo.Object{ + "k1": &tengo.Int{Value: 1}, + "k2": &tengo.Int{Value: 2}, + "k3": &tengo.Array{Value: []tengo.Object{ + &tengo.Int{Value: 3}, + &tengo.Int{Value: 4}, + &tengo.Int{Value: 5}, + }}, + }}, 7) + testCountObjects(t, &tengo.String{Value: "foo bar"}, 1) + testCountObjects(t, &tengo.Time{Value: time.Now()}, 1) + testCountObjects(t, tengo.UndefinedValue, 1) +} + +func testCountObjects(t *testing.T, o tengo.Object, expected int) { + require.Equal(t, expected, tengo.CountObjects(o)) +} + +func assertInstructionString( + t *testing.T, + instructions [][]byte, + expected string, +) { + concatted := make([]byte, 0) + for _, e := range instructions { + concatted = append(concatted, e...) + } + require.Equal(t, expected, strings.Join( + internal.FormatInstructions(concatted, 0), "\n")) +} + +func makeInstruction( + t *testing.T, + expected []byte, + opcode internal.Opcode, + operands ...int, +) { + inst := internal.MakeInstruction(opcode, operands...) + require.Equal(t, expected, inst) +} diff --git a/script/variable.go b/variable.go similarity index 51% rename from script/variable.go rename to variable.go index df34511..481b36b 100644 --- a/script/variable.go +++ b/variable.go @@ -1,24 +1,21 @@ -package script +package tengo import ( "errors" - - "github.com/d5/tengo/objects" ) // Variable is a user-defined variable for the script. type Variable struct { name string - value objects.Object + value Object } // NewVariable creates a Variable. func NewVariable(name string, value interface{}) (*Variable, error) { - obj, err := objects.FromInterface(value) + obj, err := FromInterface(value) if err != nil { return nil, err } - return &Variable{ name: name, value: obj, @@ -32,7 +29,7 @@ func (v *Variable) Name() string { // Value returns an empty interface of the variable value. func (v *Variable) Value() interface{} { - return objects.ToInterface(v.value) + return ToInterface(v.value) } // ValueType returns the name of the value type. @@ -43,107 +40,97 @@ func (v *Variable) ValueType() string { // Int returns int value of the variable value. // It returns 0 if the value is not convertible to int. func (v *Variable) Int() int { - c, _ := objects.ToInt(v.value) - + c, _ := ToInt(v.value) return c } -// Int64 returns int64 value of the variable value. -// It returns 0 if the value is not convertible to int64. +// Int64 returns int64 value of the variable value. It returns 0 if the value +// is not convertible to int64. func (v *Variable) Int64() int64 { - c, _ := objects.ToInt64(v.value) - + c, _ := ToInt64(v.value) return c } -// Float returns float64 value of the variable value. -// It returns 0.0 if the value is not convertible to float64. +// Float returns float64 value of the variable value. It returns 0.0 if the +// value is not convertible to float64. func (v *Variable) Float() float64 { - c, _ := objects.ToFloat64(v.value) - + c, _ := ToFloat64(v.value) return c } -// Char returns rune value of the variable value. -// It returns 0 if the value is not convertible to rune. +// Char returns rune value of the variable value. It returns 0 if the value is +// not convertible to rune. func (v *Variable) Char() rune { - c, _ := objects.ToRune(v.value) - + c, _ := ToRune(v.value) return c } -// Bool returns bool value of the variable value. -// It returns 0 if the value is not convertible to bool. +// Bool returns bool value of the variable value. It returns 0 if the value is +// not convertible to bool. func (v *Variable) Bool() bool { - c, _ := objects.ToBool(v.value) - + c, _ := ToBool(v.value) return c } -// Array returns []interface value of the variable value. -// It returns 0 if the value is not convertible to []interface. +// Array returns []interface value of the variable value. It returns 0 if the +// value is not convertible to []interface. func (v *Variable) Array() []interface{} { switch val := v.value.(type) { - case *objects.Array: + case *Array: var arr []interface{} for _, e := range val.Value { - arr = append(arr, objects.ToInterface(e)) + arr = append(arr, ToInterface(e)) } return arr } - return nil } -// Map returns map[string]interface{} value of the variable value. -// It returns 0 if the value is not convertible to map[string]interface{}. +// Map returns map[string]interface{} value of the variable value. It returns +// 0 if the value is not convertible to map[string]interface{}. func (v *Variable) Map() map[string]interface{} { switch val := v.value.(type) { - case *objects.Map: + case *Map: kv := make(map[string]interface{}) for mk, mv := range val.Value { - kv[mk] = objects.ToInterface(mv) + kv[mk] = ToInterface(mv) } return kv } - return nil } -// String returns string value of the variable value. -// It returns 0 if the value is not convertible to string. +// String returns string value of the variable value. It returns 0 if the value +// is not convertible to string. func (v *Variable) String() string { - c, _ := objects.ToString(v.value) - + c, _ := ToString(v.value) return c } -// Bytes returns a byte slice of the variable value. -// It returns nil if the value is not convertible to byte slice. +// Bytes returns a byte slice of the variable value. It returns nil if the +// value is not convertible to byte slice. func (v *Variable) Bytes() []byte { - c, _ := objects.ToByteSlice(v.value) - + c, _ := ToByteSlice(v.value) return c } -// Error returns an error if the underlying value is error object. -// If not, this returns nil. +// Error returns an error if the underlying value is error object. If not, +// this returns nil. func (v *Variable) Error() error { - err, ok := v.value.(*objects.Error) + err, ok := v.value.(*Error) if ok { return errors.New(err.String()) } - return nil } -// Object returns an underlying Object of the variable value. -// Note that returned Object is a copy of an actual Object used in the script. -func (v *Variable) Object() objects.Object { +// Object returns an underlying Object of the variable value. Note that +// returned Object is a copy of an actual Object used in the script. +func (v *Variable) Object() Object { return v.value } // IsUndefined returns true if the underlying value is undefined. func (v *Variable) IsUndefined() bool { - return v.value == objects.UndefinedValue + return v.value == UndefinedValue } diff --git a/variable_test.go b/variable_test.go new file mode 100644 index 0000000..ff8f03d --- /dev/null +++ b/variable_test.go @@ -0,0 +1,81 @@ +package tengo_test + +import ( + "testing" + + "github.com/d5/tengo" + "github.com/d5/tengo/internal/require" +) + +type VariableTest struct { + Name string + Value interface{} + ValueType string + IntValue int + Int64Value int64 + FloatValue float64 + CharValue rune + BoolValue bool + StringValue string + Object tengo.Object + IsUndefined bool +} + +func TestVariable(t *testing.T) { + vars := []VariableTest{ + { + Name: "a", + Value: int64(1), + ValueType: "int", + IntValue: 1, + Int64Value: 1, + FloatValue: 1.0, + CharValue: rune(1), + BoolValue: true, + StringValue: "1", + Object: &tengo.Int{Value: 1}, + }, + { + Name: "b", + Value: "52.11", + ValueType: "string", + FloatValue: 52.11, + StringValue: "52.11", + BoolValue: true, + Object: &tengo.String{Value: "52.11"}, + }, + { + Name: "c", + Value: true, + ValueType: "bool", + IntValue: 1, + Int64Value: 1, + FloatValue: 0, + BoolValue: true, + StringValue: "true", + Object: tengo.TrueValue, + }, + { + Name: "d", + Value: nil, + ValueType: "undefined", + Object: tengo.UndefinedValue, + IsUndefined: true, + }, + } + + for _, tc := range vars { + v, err := tengo.NewVariable(tc.Name, tc.Value) + require.NoError(t, err) + require.Equal(t, tc.Value, v.Value(), "Name: %s", tc.Name) + require.Equal(t, tc.ValueType, v.ValueType(), "Name: %s", tc.Name) + require.Equal(t, tc.IntValue, v.Int(), "Name: %s", tc.Name) + require.Equal(t, tc.Int64Value, v.Int64(), "Name: %s", tc.Name) + require.Equal(t, tc.FloatValue, v.Float(), "Name: %s", tc.Name) + require.Equal(t, tc.CharValue, v.Char(), "Name: %s", tc.Name) + require.Equal(t, tc.BoolValue, v.Bool(), "Name: %s", tc.Name) + require.Equal(t, tc.StringValue, v.String(), "Name: %s", tc.Name) + require.Equal(t, tc.Object, v.Object(), "Name: %s", tc.Name) + require.Equal(t, tc.IsUndefined, v.IsUndefined(), "Name: %s", tc.Name) + } +} diff --git a/runtime/vm.go b/vm.go similarity index 56% rename from runtime/vm.go rename to vm.go index 07f6e53..3332017 100644 --- a/runtime/vm.go +++ b/vm.go @@ -1,36 +1,31 @@ -package runtime +package tengo import ( "fmt" "sync/atomic" - "github.com/d5/tengo/compiler" - "github.com/d5/tengo/compiler/source" - "github.com/d5/tengo/compiler/token" - "github.com/d5/tengo/objects" + "github.com/d5/tengo/internal" + "github.com/d5/tengo/internal/token" ) -const ( - // StackSize is the maximum stack size. - StackSize = 2048 - - // GlobalsSize is the maximum number of global variables. - GlobalsSize = 1024 - - // MaxFrames is the maximum number of function frames. - MaxFrames = 1024 -) +// frame represents a function call frame. +type frame struct { + fn *CompiledFunction + freeVars []*ObjectPtr + ip int + basePointer int +} // VM is a virtual machine that executes the bytecode compiled by Compiler. type VM struct { - constants []objects.Object - stack [StackSize]objects.Object + constants []Object + stack [StackSize]Object sp int - globals []objects.Object - fileSet *source.FileSet - frames [MaxFrames]Frame + globals []Object + fileSet *internal.SourceFileSet + frames [MaxFrames]frame framesIndex int - curFrame *Frame + curFrame *frame curInsts []byte ip int aborting int64 @@ -40,11 +35,14 @@ type VM struct { } // NewVM creates a VM. -func NewVM(bytecode *compiler.Bytecode, globals []objects.Object, maxAllocs int64) *VM { +func NewVM( + bytecode *Bytecode, + globals []Object, + maxAllocs int64, +) *VM { if globals == nil { - globals = make([]objects.Object, GlobalsSize) + globals = make([]Object, GlobalsSize) } - v := &VM{ constants: bytecode.Constants, sp: 0, @@ -54,12 +52,10 @@ func NewVM(bytecode *compiler.Bytecode, globals []objects.Object, maxAllocs int6 ip: -1, maxAllocs: maxAllocs, } - v.frames[0].fn = bytecode.MainFunction v.frames[0].ip = -1 v.curFrame = &v.frames[0] v.curInsts = v.curFrame.fn.Instructions - return v } @@ -79,75 +75,52 @@ func (v *VM) Run() (err error) { v.allocs = v.maxAllocs + 1 v.run() - atomic.StoreInt64(&v.aborting, 0) - err = v.err if err != nil { - filePos := v.fileSet.Position(v.curFrame.fn.SourcePos(v.ip - 1)) - err = fmt.Errorf("Runtime Error: %s\n\tat %s", err.Error(), filePos) + filePos := v.fileSet.Position( + v.curFrame.fn.SourcePos(v.ip - 1)) + err = fmt.Errorf("Runtime Error: %s\n\tat %s", + err.Error(), filePos) for v.framesIndex > 1 { v.framesIndex-- v.curFrame = &v.frames[v.framesIndex-1] - - filePos = v.fileSet.Position(v.curFrame.fn.SourcePos(v.curFrame.ip - 1)) + filePos = v.fileSet.Position( + v.curFrame.fn.SourcePos(v.curFrame.ip - 1)) err = fmt.Errorf("%s\n\tat %s", err.Error(), filePos) } return err } - return nil } func (v *VM) run() { - defer func() { - if r := recover(); r != nil { - if v.sp >= StackSize || v.framesIndex >= MaxFrames { - v.err = ErrStackOverflow - return - } - - if v.ip < len(v.curInsts)-1 { - if err, ok := r.(error); ok { - v.err = err - } else { - v.err = fmt.Errorf("panic: %v", r) - } - } - } - }() - for atomic.LoadInt64(&v.aborting) == 0 { v.ip++ switch v.curInsts[v.ip] { - case compiler.OpConstant: + case internal.OpConstant: v.ip += 2 cidx := int(v.curInsts[v.ip]) | int(v.curInsts[v.ip-1])<<8 v.stack[v.sp] = v.constants[cidx] v.sp++ - - case compiler.OpNull: - v.stack[v.sp] = objects.UndefinedValue + case internal.OpNull: + v.stack[v.sp] = UndefinedValue v.sp++ - - case compiler.OpBinaryOp: + case internal.OpBinaryOp: v.ip++ right := v.stack[v.sp-1] left := v.stack[v.sp-2] - tok := token.Token(v.curInsts[v.ip]) res, e := left.BinaryOp(tok, right) if e != nil { v.sp -= 2 - - if e == objects.ErrInvalidOperator { + if e == ErrInvalidOperator { v.err = fmt.Errorf("invalid operation: %s %s %s", left.TypeName(), tok.String(), right.TypeName()) return } - v.err = e return } @@ -160,185 +133,155 @@ func (v *VM) run() { v.stack[v.sp-2] = res v.sp-- - - case compiler.OpEqual: + case internal.OpEqual: right := v.stack[v.sp-1] left := v.stack[v.sp-2] v.sp -= 2 - if left.Equals(right) { - v.stack[v.sp] = objects.TrueValue + v.stack[v.sp] = TrueValue } else { - v.stack[v.sp] = objects.FalseValue + v.stack[v.sp] = FalseValue } v.sp++ - - case compiler.OpNotEqual: + case internal.OpNotEqual: right := v.stack[v.sp-1] left := v.stack[v.sp-2] v.sp -= 2 - if left.Equals(right) { - v.stack[v.sp] = objects.FalseValue + v.stack[v.sp] = FalseValue } else { - v.stack[v.sp] = objects.TrueValue + v.stack[v.sp] = TrueValue } v.sp++ - - case compiler.OpPop: + case internal.OpPop: v.sp-- - - case compiler.OpTrue: - v.stack[v.sp] = objects.TrueValue + case internal.OpTrue: + v.stack[v.sp] = TrueValue v.sp++ - - case compiler.OpFalse: - v.stack[v.sp] = objects.FalseValue + case internal.OpFalse: + v.stack[v.sp] = FalseValue v.sp++ - - case compiler.OpLNot: + case internal.OpLNot: operand := v.stack[v.sp-1] v.sp-- - if operand.IsFalsy() { - v.stack[v.sp] = objects.TrueValue + v.stack[v.sp] = TrueValue } else { - v.stack[v.sp] = objects.FalseValue + v.stack[v.sp] = FalseValue } v.sp++ - - case compiler.OpBComplement: + case internal.OpBComplement: operand := v.stack[v.sp-1] v.sp-- switch x := operand.(type) { - case *objects.Int: - var res objects.Object = &objects.Int{Value: ^x.Value} - + case *Int: + var res Object = &Int{Value: ^x.Value} v.allocs-- if v.allocs == 0 { v.err = ErrObjectAllocLimit return } - v.stack[v.sp] = res v.sp++ default: - v.err = fmt.Errorf("invalid operation: ^%s", operand.TypeName()) + v.err = fmt.Errorf("invalid operation: ^%s", + operand.TypeName()) return } - - case compiler.OpMinus: + case internal.OpMinus: operand := v.stack[v.sp-1] v.sp-- switch x := operand.(type) { - case *objects.Int: - var res objects.Object = &objects.Int{Value: -x.Value} - + case *Int: + var res Object = &Int{Value: -x.Value} v.allocs-- if v.allocs == 0 { v.err = ErrObjectAllocLimit return } - v.stack[v.sp] = res v.sp++ - case *objects.Float: - var res objects.Object = &objects.Float{Value: -x.Value} - + case *Float: + var res Object = &Float{Value: -x.Value} v.allocs-- if v.allocs == 0 { v.err = ErrObjectAllocLimit return } - v.stack[v.sp] = res v.sp++ default: - v.err = fmt.Errorf("invalid operation: -%s", operand.TypeName()) + v.err = fmt.Errorf("invalid operation: -%s", + operand.TypeName()) return } - - case compiler.OpJumpFalsy: + case internal.OpJumpFalsy: v.ip += 2 v.sp-- if v.stack[v.sp].IsFalsy() { pos := int(v.curInsts[v.ip]) | int(v.curInsts[v.ip-1])<<8 v.ip = pos - 1 } - - case compiler.OpAndJump: + case internal.OpAndJump: v.ip += 2 - if v.stack[v.sp-1].IsFalsy() { pos := int(v.curInsts[v.ip]) | int(v.curInsts[v.ip-1])<<8 v.ip = pos - 1 } else { v.sp-- } - - case compiler.OpOrJump: + case internal.OpOrJump: v.ip += 2 - if v.stack[v.sp-1].IsFalsy() { v.sp-- } else { pos := int(v.curInsts[v.ip]) | int(v.curInsts[v.ip-1])<<8 v.ip = pos - 1 } - - case compiler.OpJump: + case internal.OpJump: pos := int(v.curInsts[v.ip+2]) | int(v.curInsts[v.ip+1])<<8 v.ip = pos - 1 - - case compiler.OpSetGlobal: + case internal.OpSetGlobal: v.ip += 2 v.sp-- - globalIndex := int(v.curInsts[v.ip]) | int(v.curInsts[v.ip-1])<<8 v.globals[globalIndex] = v.stack[v.sp] - - case compiler.OpSetSelGlobal: + case internal.OpSetSelGlobal: v.ip += 3 globalIndex := int(v.curInsts[v.ip-1]) | int(v.curInsts[v.ip-2])<<8 numSelectors := int(v.curInsts[v.ip]) // selectors and RHS value - selectors := make([]objects.Object, numSelectors) + selectors := make([]Object, numSelectors) for i := 0; i < numSelectors; i++ { selectors[i] = v.stack[v.sp-numSelectors+i] } - val := v.stack[v.sp-numSelectors-1] v.sp -= numSelectors + 1 - - if e := indexAssign(v.globals[globalIndex], val, selectors); e != nil { + e := indexAssign(v.globals[globalIndex], val, selectors) + if e != nil { v.err = e return } - - case compiler.OpGetGlobal: + case internal.OpGetGlobal: v.ip += 2 globalIndex := int(v.curInsts[v.ip]) | int(v.curInsts[v.ip-1])<<8 - val := v.globals[globalIndex] - v.stack[v.sp] = val v.sp++ - - case compiler.OpArray: + case internal.OpArray: v.ip += 2 numElements := int(v.curInsts[v.ip]) | int(v.curInsts[v.ip-1])<<8 - var elements []objects.Object + var elements []Object for i := v.sp - numElements; i < v.sp; i++ { elements = append(elements, v.stack[i]) } v.sp -= numElements - var arr objects.Object = &objects.Array{Value: elements} - + var arr Object = &Array{Value: elements} v.allocs-- if v.allocs == 0 { v.err = ErrObjectAllocLimit @@ -347,355 +290,260 @@ func (v *VM) run() { v.stack[v.sp] = arr v.sp++ - - case compiler.OpMap: + case internal.OpMap: v.ip += 2 numElements := int(v.curInsts[v.ip]) | int(v.curInsts[v.ip-1])<<8 - - kv := make(map[string]objects.Object) + kv := make(map[string]Object) for i := v.sp - numElements; i < v.sp; i += 2 { key := v.stack[i] value := v.stack[i+1] - kv[key.(*objects.String).Value] = value + kv[key.(*String).Value] = value } v.sp -= numElements - var m objects.Object = &objects.Map{Value: kv} - + var m Object = &Map{Value: kv} v.allocs-- if v.allocs == 0 { v.err = ErrObjectAllocLimit return } - v.stack[v.sp] = m v.sp++ - - case compiler.OpError: + case internal.OpError: value := v.stack[v.sp-1] - - var e objects.Object = &objects.Error{ + var e Object = &Error{ Value: value, } - v.allocs-- if v.allocs == 0 { v.err = ErrObjectAllocLimit return } - v.stack[v.sp-1] = e - - case compiler.OpImmutable: + case internal.OpImmutable: value := v.stack[v.sp-1] - switch value := value.(type) { - case *objects.Array: - var immutableArray objects.Object = &objects.ImmutableArray{ + case *Array: + var immutableArray Object = &ImmutableArray{ Value: value.Value, } - v.allocs-- if v.allocs == 0 { v.err = ErrObjectAllocLimit return } - v.stack[v.sp-1] = immutableArray - case *objects.Map: - var immutableMap objects.Object = &objects.ImmutableMap{ + case *Map: + var immutableMap Object = &ImmutableMap{ Value: value.Value, } - v.allocs-- if v.allocs == 0 { v.err = ErrObjectAllocLimit return } - v.stack[v.sp-1] = immutableMap } - - case compiler.OpIndex: + case internal.OpIndex: index := v.stack[v.sp-1] left := v.stack[v.sp-2] v.sp -= 2 - switch left := left.(type) { - case objects.Indexable: - val, e := left.IndexGet(index) - if e != nil { - - if e == objects.ErrInvalidIndexType { - v.err = fmt.Errorf("invalid index type: %s", index.TypeName()) - return - } - - v.err = e + val, err := left.IndexGet(index) + if err != nil { + if err == ErrNotIndexable { + v.err = fmt.Errorf("not indexable: %s", index.TypeName()) return } - if val == nil { - val = objects.UndefinedValue - } - - v.stack[v.sp] = val - v.sp++ - - case *objects.Error: // e.value - key, ok := index.(*objects.String) - if !ok || key.Value != "value" { - v.err = fmt.Errorf("invalid index on error") + if err == ErrInvalidIndexType { + v.err = fmt.Errorf("invalid index type: %s", + index.TypeName()) return } - - v.stack[v.sp] = left.Value - v.sp++ - - default: - v.err = fmt.Errorf("not indexable: %s", left.TypeName()) + v.err = err return } - - case compiler.OpSliceIndex: + if val == nil { + val = UndefinedValue + } + v.stack[v.sp] = val + v.sp++ + case internal.OpSliceIndex: high := v.stack[v.sp-1] low := v.stack[v.sp-2] left := v.stack[v.sp-3] v.sp -= 3 var lowIdx int64 - if low != objects.UndefinedValue { - if low, ok := low.(*objects.Int); ok { + if low != UndefinedValue { + if low, ok := low.(*Int); ok { lowIdx = low.Value } else { - v.err = fmt.Errorf("invalid slice index type: %s", low.TypeName()) + v.err = fmt.Errorf("invalid slice index type: %s", + low.TypeName()) return } } switch left := left.(type) { - case *objects.Array: + case *Array: numElements := int64(len(left.Value)) var highIdx int64 - if high == objects.UndefinedValue { + if high == UndefinedValue { highIdx = numElements - } else if high, ok := high.(*objects.Int); ok { + } else if high, ok := high.(*Int); ok { highIdx = high.Value } else { - v.err = fmt.Errorf("invalid slice index type: %s", high.TypeName()) + v.err = fmt.Errorf("invalid slice index type: %s", + high.TypeName()) return } - if lowIdx > highIdx { - v.err = fmt.Errorf("invalid slice index: %d > %d", lowIdx, highIdx) + v.err = fmt.Errorf("invalid slice index: %d > %d", + lowIdx, highIdx) return } - if lowIdx < 0 { lowIdx = 0 } else if lowIdx > numElements { lowIdx = numElements } - if highIdx < 0 { highIdx = 0 } else if highIdx > numElements { highIdx = numElements } - - var val objects.Object = &objects.Array{Value: left.Value[lowIdx:highIdx]} - + var val Object = &Array{ + Value: left.Value[lowIdx:highIdx], + } v.allocs-- if v.allocs == 0 { v.err = ErrObjectAllocLimit return } - v.stack[v.sp] = val v.sp++ - - case *objects.ImmutableArray: + case *ImmutableArray: numElements := int64(len(left.Value)) var highIdx int64 - if high == objects.UndefinedValue { + if high == UndefinedValue { highIdx = numElements - } else if high, ok := high.(*objects.Int); ok { + } else if high, ok := high.(*Int); ok { highIdx = high.Value } else { - v.err = fmt.Errorf("invalid slice index type: %s", high.TypeName()) + v.err = fmt.Errorf("invalid slice index type: %s", + high.TypeName()) return } - if lowIdx > highIdx { - v.err = fmt.Errorf("invalid slice index: %d > %d", lowIdx, highIdx) + v.err = fmt.Errorf("invalid slice index: %d > %d", + lowIdx, highIdx) return } - if lowIdx < 0 { lowIdx = 0 } else if lowIdx > numElements { lowIdx = numElements } - if highIdx < 0 { highIdx = 0 } else if highIdx > numElements { highIdx = numElements } - - var val objects.Object = &objects.Array{Value: left.Value[lowIdx:highIdx]} - + var val Object = &Array{ + Value: left.Value[lowIdx:highIdx], + } v.allocs-- if v.allocs == 0 { v.err = ErrObjectAllocLimit return } - v.stack[v.sp] = val v.sp++ - - case *objects.String: + case *String: numElements := int64(len(left.Value)) var highIdx int64 - if high == objects.UndefinedValue { + if high == UndefinedValue { highIdx = numElements - } else if high, ok := high.(*objects.Int); ok { + } else if high, ok := high.(*Int); ok { highIdx = high.Value } else { - v.err = fmt.Errorf("invalid slice index type: %s", high.TypeName()) + v.err = fmt.Errorf("invalid slice index type: %s", + high.TypeName()) return } - if lowIdx > highIdx { - v.err = fmt.Errorf("invalid slice index: %d > %d", lowIdx, highIdx) + v.err = fmt.Errorf("invalid slice index: %d > %d", + lowIdx, highIdx) return } - if lowIdx < 0 { lowIdx = 0 } else if lowIdx > numElements { lowIdx = numElements } - if highIdx < 0 { highIdx = 0 } else if highIdx > numElements { highIdx = numElements } - - var val objects.Object = &objects.String{Value: left.Value[lowIdx:highIdx]} - + var val Object = &String{ + Value: left.Value[lowIdx:highIdx], + } v.allocs-- if v.allocs == 0 { v.err = ErrObjectAllocLimit return } - v.stack[v.sp] = val v.sp++ - - case *objects.Bytes: + case *Bytes: numElements := int64(len(left.Value)) var highIdx int64 - if high == objects.UndefinedValue { + if high == UndefinedValue { highIdx = numElements - } else if high, ok := high.(*objects.Int); ok { + } else if high, ok := high.(*Int); ok { highIdx = high.Value } else { - v.err = fmt.Errorf("invalid slice index type: %s", high.TypeName()) + v.err = fmt.Errorf("invalid slice index type: %s", + high.TypeName()) return } - if lowIdx > highIdx { - v.err = fmt.Errorf("invalid slice index: %d > %d", lowIdx, highIdx) + v.err = fmt.Errorf("invalid slice index: %d > %d", + lowIdx, highIdx) return } - if lowIdx < 0 { lowIdx = 0 } else if lowIdx > numElements { lowIdx = numElements } - if highIdx < 0 { highIdx = 0 } else if highIdx > numElements { highIdx = numElements } - - var val objects.Object = &objects.Bytes{Value: left.Value[lowIdx:highIdx]} - + var val Object = &Bytes{ + Value: left.Value[lowIdx:highIdx], + } v.allocs-- if v.allocs == 0 { v.err = ErrObjectAllocLimit return } - v.stack[v.sp] = val v.sp++ } - - case compiler.OpCall: + case internal.OpCall: numArgs := int(v.curInsts[v.ip+1]) v.ip++ - value := v.stack[v.sp-1-numArgs] - - switch callee := value.(type) { - case *objects.Closure: - if callee.Fn.VarArgs { - // if the closure is variadic, - // roll up all variadic parameters into an array - realArgs := callee.Fn.NumParameters - 1 - varArgs := numArgs - realArgs - if varArgs >= 0 { - numArgs = realArgs + 1 - args := make([]objects.Object, varArgs) - spStart := v.sp - varArgs - for i := spStart; i < v.sp; i++ { - args[i-spStart] = v.stack[i] - } - v.stack[spStart] = &objects.Array{Value: args} - v.sp = spStart + 1 - } - } - - if numArgs != callee.Fn.NumParameters { - if callee.Fn.VarArgs { - v.err = fmt.Errorf("wrong number of arguments: want>=%d, got=%d", - callee.Fn.NumParameters-1, numArgs) - } else { - v.err = fmt.Errorf("wrong number of arguments: want=%d, got=%d", - callee.Fn.NumParameters, numArgs) - } - return - } - - // test if it's tail-call - if callee.Fn == v.curFrame.fn { // recursion - nextOp := v.curInsts[v.ip+1] - if nextOp == compiler.OpReturn || - (nextOp == compiler.OpPop && compiler.OpReturn == v.curInsts[v.ip+2]) { - for p := 0; p < numArgs; p++ { - v.stack[v.curFrame.basePointer+p] = v.stack[v.sp-numArgs+p] - } - v.sp -= numArgs + 1 - v.ip = -1 // reset IP to beginning of the frame - continue - } - } - - // update call frame - v.curFrame.ip = v.ip // store current ip before call - v.curFrame = &(v.frames[v.framesIndex]) - v.curFrame.fn = callee.Fn - v.curFrame.freeVars = callee.Free - v.curFrame.basePointer = v.sp - numArgs - v.curInsts = callee.Fn.Instructions - v.ip = -1 - v.framesIndex++ - v.sp = v.sp - numArgs + callee.Fn.NumLocals - - case *objects.CompiledFunction: + if !value.CanCall() { + v.err = fmt.Errorf("not callable: %s", value.TypeName()) + return + } + if callee, ok := value.(*CompiledFunction); ok { if callee.VarArgs { // if the closure is variadic, // roll up all variadic parameters into an array @@ -703,22 +551,23 @@ func (v *VM) run() { varArgs := numArgs - realArgs if varArgs >= 0 { numArgs = realArgs + 1 - args := make([]objects.Object, varArgs) + args := make([]Object, varArgs) spStart := v.sp - varArgs for i := spStart; i < v.sp; i++ { args[i-spStart] = v.stack[i] } - v.stack[spStart] = &objects.Array{Value: args} + v.stack[spStart] = &Array{Value: args} v.sp = spStart + 1 } } - if numArgs != callee.NumParameters { if callee.VarArgs { - v.err = fmt.Errorf("wrong number of arguments: want>=%d, got=%d", + v.err = fmt.Errorf( + "wrong number of arguments: want>=%d, got=%d", callee.NumParameters-1, numArgs) } else { - v.err = fmt.Errorf("wrong number of arguments: want=%d, got=%d", + v.err = fmt.Errorf( + "wrong number of arguments: want=%d, got=%d", callee.NumParameters, numArgs) } return @@ -727,321 +576,271 @@ func (v *VM) run() { // test if it's tail-call if callee == v.curFrame.fn { // recursion nextOp := v.curInsts[v.ip+1] - if nextOp == compiler.OpReturn || - (nextOp == compiler.OpPop && compiler.OpReturn == v.curInsts[v.ip+2]) { + if nextOp == internal.OpReturn || + (nextOp == internal.OpPop && + internal.OpReturn == v.curInsts[v.ip+2]) { for p := 0; p < numArgs; p++ { - v.stack[v.curFrame.basePointer+p] = v.stack[v.sp-numArgs+p] + v.stack[v.curFrame.basePointer+p] = + v.stack[v.sp-numArgs+p] } v.sp -= numArgs + 1 v.ip = -1 // reset IP to beginning of the frame continue } } + if v.framesIndex >= MaxFrames { + v.err = ErrStackOverflow + return + } // update call frame v.curFrame.ip = v.ip // store current ip before call v.curFrame = &(v.frames[v.framesIndex]) v.curFrame.fn = callee - v.curFrame.freeVars = nil + v.curFrame.freeVars = callee.Free v.curFrame.basePointer = v.sp - numArgs v.curInsts = callee.Instructions v.ip = -1 v.framesIndex++ v.sp = v.sp - numArgs + callee.NumLocals - - case objects.Callable: - var args []objects.Object + } else { + var args []Object args = append(args, v.stack[v.sp-numArgs:v.sp]...) - - ret, e := callee.Call(args...) + ret, e := value.Call(args...) v.sp -= numArgs + 1 // runtime error if e != nil { - if e == objects.ErrWrongNumArguments { - v.err = fmt.Errorf("wrong number of arguments in call to '%s'", + if e == ErrWrongNumArguments { + v.err = fmt.Errorf( + "wrong number of arguments in call to '%s'", value.TypeName()) return } - - if e, ok := e.(objects.ErrInvalidArgumentType); ok { - v.err = fmt.Errorf("invalid type for argument '%s' in call to '%s': expected %s, found %s", + if e, ok := e.(ErrInvalidArgumentType); ok { + v.err = fmt.Errorf( + "invalid type for argument '%s' in call to '%s': "+ + "expected %s, found %s", e.Name, value.TypeName(), e.Expected, e.Found) return } - v.err = e return } // nil return -> undefined if ret == nil { - ret = objects.UndefinedValue + ret = UndefinedValue } - v.allocs-- if v.allocs == 0 { v.err = ErrObjectAllocLimit return } - v.stack[v.sp] = ret v.sp++ - - default: - v.err = fmt.Errorf("not callable: %s", callee.TypeName()) - return } - - case compiler.OpReturn: + case internal.OpReturn: v.ip++ - var retVal objects.Object + var retVal Object if int(v.curInsts[v.ip]) == 1 { retVal = v.stack[v.sp-1] } else { - retVal = objects.UndefinedValue + retVal = UndefinedValue } //v.sp-- - v.framesIndex-- v.curFrame = &v.frames[v.framesIndex-1] v.curInsts = v.curFrame.fn.Instructions v.ip = v.curFrame.ip - //v.sp = lastFrame.basePointer - 1 v.sp = v.frames[v.framesIndex].basePointer - // skip stack overflow check because (newSP) <= (oldSP) v.stack[v.sp-1] = retVal //v.sp++ - - case compiler.OpDefineLocal: + case internal.OpDefineLocal: v.ip++ localIndex := int(v.curInsts[v.ip]) - sp := v.curFrame.basePointer + localIndex // local variables can be mutated by other actions // so always store the copy of popped value val := v.stack[v.sp-1] v.sp-- - v.stack[sp] = val - - case compiler.OpSetLocal: + case internal.OpSetLocal: localIndex := int(v.curInsts[v.ip+1]) v.ip++ - sp := v.curFrame.basePointer + localIndex - // update pointee of v.stack[sp] instead of replacing the pointer itself. - // this is needed because there can be free variables referencing the same local variables. + // update pointee of v.stack[sp] instead of replacing the pointer + // itself. this is needed because there can be free variables + // referencing the same local variables. val := v.stack[v.sp-1] v.sp-- - - if obj, ok := v.stack[sp].(*objects.ObjectPtr); ok { + if obj, ok := v.stack[sp].(*ObjectPtr); ok { *obj.Value = val val = obj } v.stack[sp] = val // also use a copy of popped value - - case compiler.OpSetSelLocal: + case internal.OpSetSelLocal: localIndex := int(v.curInsts[v.ip+1]) numSelectors := int(v.curInsts[v.ip+2]) v.ip += 2 // selectors and RHS value - selectors := make([]objects.Object, numSelectors) + selectors := make([]Object, numSelectors) for i := 0; i < numSelectors; i++ { selectors[i] = v.stack[v.sp-numSelectors+i] } - val := v.stack[v.sp-numSelectors-1] v.sp -= numSelectors + 1 - dst := v.stack[v.curFrame.basePointer+localIndex] - if obj, ok := dst.(*objects.ObjectPtr); ok { + if obj, ok := dst.(*ObjectPtr); ok { dst = *obj.Value } - if e := indexAssign(dst, val, selectors); e != nil { v.err = e return } - - case compiler.OpGetLocal: + case internal.OpGetLocal: v.ip++ localIndex := int(v.curInsts[v.ip]) - val := v.stack[v.curFrame.basePointer+localIndex] - - if obj, ok := val.(*objects.ObjectPtr); ok { + if obj, ok := val.(*ObjectPtr); ok { val = *obj.Value } - v.stack[v.sp] = val v.sp++ - - case compiler.OpGetBuiltin: + case internal.OpGetBuiltin: v.ip++ builtinIndex := int(v.curInsts[v.ip]) - - v.stack[v.sp] = objects.Builtins[builtinIndex] + v.stack[v.sp] = builtinFuncs[builtinIndex] v.sp++ - - case compiler.OpClosure: + case internal.OpClosure: v.ip += 3 constIndex := int(v.curInsts[v.ip-1]) | int(v.curInsts[v.ip-2])<<8 numFree := int(v.curInsts[v.ip]) - - fn, ok := v.constants[constIndex].(*objects.CompiledFunction) + fn, ok := v.constants[constIndex].(*CompiledFunction) if !ok { v.err = fmt.Errorf("not function: %s", fn.TypeName()) return } - - free := make([]*objects.ObjectPtr, numFree) + free := make([]*ObjectPtr, numFree) for i := 0; i < numFree; i++ { switch freeVar := (v.stack[v.sp-numFree+i]).(type) { - case *objects.ObjectPtr: + case *ObjectPtr: free[i] = freeVar default: - free[i] = &objects.ObjectPtr{Value: &v.stack[v.sp-numFree+i]} + free[i] = &ObjectPtr{ + Value: &v.stack[v.sp-numFree+i], + } } } - v.sp -= numFree - - var cl = &objects.Closure{ - Fn: fn, - Free: free, + cl := &CompiledFunction{ + Instructions: fn.Instructions, + NumLocals: fn.NumLocals, + NumParameters: fn.NumParameters, + VarArgs: fn.VarArgs, + Free: free, } - v.allocs-- if v.allocs == 0 { v.err = ErrObjectAllocLimit return } - v.stack[v.sp] = cl v.sp++ - - case compiler.OpGetFreePtr: + case internal.OpGetFreePtr: v.ip++ freeIndex := int(v.curInsts[v.ip]) - val := v.curFrame.freeVars[freeIndex] - v.stack[v.sp] = val v.sp++ - - case compiler.OpGetFree: + case internal.OpGetFree: v.ip++ freeIndex := int(v.curInsts[v.ip]) - val := *v.curFrame.freeVars[freeIndex].Value - v.stack[v.sp] = val v.sp++ - - case compiler.OpSetFree: + case internal.OpSetFree: v.ip++ freeIndex := int(v.curInsts[v.ip]) - *v.curFrame.freeVars[freeIndex].Value = v.stack[v.sp-1] - v.sp-- - - case compiler.OpGetLocalPtr: + case internal.OpGetLocalPtr: v.ip++ localIndex := int(v.curInsts[v.ip]) - sp := v.curFrame.basePointer + localIndex val := v.stack[sp] - - var freeVar *objects.ObjectPtr - if obj, ok := val.(*objects.ObjectPtr); ok { + var freeVar *ObjectPtr + if obj, ok := val.(*ObjectPtr); ok { freeVar = obj } else { - freeVar = &objects.ObjectPtr{Value: &val} + freeVar = &ObjectPtr{Value: &val} v.stack[sp] = freeVar } - v.stack[v.sp] = freeVar v.sp++ - - case compiler.OpSetSelFree: + case internal.OpSetSelFree: v.ip += 2 freeIndex := int(v.curInsts[v.ip-1]) numSelectors := int(v.curInsts[v.ip]) // selectors and RHS value - selectors := make([]objects.Object, numSelectors) + selectors := make([]Object, numSelectors) for i := 0; i < numSelectors; i++ { selectors[i] = v.stack[v.sp-numSelectors+i] } val := v.stack[v.sp-numSelectors-1] v.sp -= numSelectors + 1 - - if e := indexAssign(*v.curFrame.freeVars[freeIndex].Value, val, selectors); e != nil { + e := indexAssign(*v.curFrame.freeVars[freeIndex].Value, + val, selectors) + if e != nil { v.err = e return } - - case compiler.OpIteratorInit: - var iterator objects.Object - + case internal.OpIteratorInit: + var iterator Object dst := v.stack[v.sp-1] v.sp-- - - iterable, ok := dst.(objects.Iterable) - if !ok { + if !dst.CanIterate() { v.err = fmt.Errorf("not iterable: %s", dst.TypeName()) return } - - iterator = iterable.Iterate() - + iterator = dst.Iterate() v.allocs-- if v.allocs == 0 { v.err = ErrObjectAllocLimit return } - v.stack[v.sp] = iterator v.sp++ - - case compiler.OpIteratorNext: + case internal.OpIteratorNext: iterator := v.stack[v.sp-1] v.sp-- - - hasMore := iterator.(objects.Iterator).Next() - + hasMore := iterator.(Iterator).Next() if hasMore { - v.stack[v.sp] = objects.TrueValue + v.stack[v.sp] = TrueValue } else { - v.stack[v.sp] = objects.FalseValue + v.stack[v.sp] = FalseValue } v.sp++ - - case compiler.OpIteratorKey: + case internal.OpIteratorKey: iterator := v.stack[v.sp-1] v.sp-- - - val := iterator.(objects.Iterator).Key() - + val := iterator.(Iterator).Key() v.stack[v.sp] = val v.sp++ - - case compiler.OpIteratorValue: + case internal.OpIteratorValue: iterator := v.stack[v.sp-1] v.sp-- - - val := iterator.(objects.Iterator).Value() - + val := iterator.(Iterator).Value() v.stack[v.sp] = val v.sp++ - + case internal.OpSuspend: + return default: v.err = fmt.Errorf("unknown opcode: %d", v.curInsts[v.ip]) return @@ -1054,39 +853,31 @@ func (v *VM) IsStackEmpty() bool { return v.sp == 0 } -func indexAssign(dst, src objects.Object, selectors []objects.Object) error { +func indexAssign(dst, src Object, selectors []Object) error { numSel := len(selectors) - for sidx := numSel - 1; sidx > 0; sidx-- { - indexable, ok := dst.(objects.Indexable) - if !ok { - return fmt.Errorf("not indexable: %s", dst.TypeName()) - } - - next, err := indexable.IndexGet(selectors[sidx]) + next, err := dst.IndexGet(selectors[sidx]) if err != nil { - if err == objects.ErrInvalidIndexType { - return fmt.Errorf("invalid index type: %s", selectors[sidx].TypeName()) + if err == ErrNotIndexable { + return fmt.Errorf("not indexable: %s", dst.TypeName()) + } + if err == ErrInvalidIndexType { + return fmt.Errorf("invalid index type: %s", + selectors[sidx].TypeName()) } - return err } - dst = next } - indexAssignable, ok := dst.(objects.IndexAssignable) - if !ok { - return fmt.Errorf("not index-assignable: %s", dst.TypeName()) - } - - if err := indexAssignable.IndexSet(selectors[0], src); err != nil { - if err == objects.ErrInvalidIndexValueType { + if err := dst.IndexSet(selectors[0], src); err != nil { + if err == ErrNotIndexAssignable { + return fmt.Errorf("not index-assignable: %s", dst.TypeName()) + } + if err == ErrInvalidIndexValueType { return fmt.Errorf("invaid index value type: %s", src.TypeName()) } - return err } - return nil } diff --git a/vm_test.go b/vm_test.go new file mode 100644 index 0000000..2c99468 --- /dev/null +++ b/vm_test.go @@ -0,0 +1,3475 @@ +package tengo_test + +import ( + "errors" + "fmt" + "math" + "math/rand" + "reflect" + _runtime "runtime" + "strings" + "testing" + + "github.com/d5/tengo" + "github.com/d5/tengo/internal" + "github.com/d5/tengo/internal/require" + "github.com/d5/tengo/internal/token" + "github.com/d5/tengo/stdlib" +) + +const testOut = "out" + +type IARR []interface{} +type IMAP map[string]interface{} +type MAP = map[string]interface{} +type ARR = []interface{} + +type testopts struct { + modules *tengo.ModuleMap + symbols map[string]tengo.Object + maxAllocs int64 + skip2ndPass bool +} + +func Opts() *testopts { + return &testopts{ + modules: tengo.NewModuleMap(), + symbols: make(map[string]tengo.Object), + maxAllocs: -1, + skip2ndPass: false, + } +} + +func (o *testopts) copy() *testopts { + c := &testopts{ + modules: o.modules.Copy(), + symbols: make(map[string]tengo.Object), + maxAllocs: o.maxAllocs, + skip2ndPass: o.skip2ndPass, + } + for k, v := range o.symbols { + c.symbols[k] = v + } + return c +} + +func (o *testopts) Stdlib() *testopts { + o.modules.AddMap(stdlib.GetModuleMap(stdlib.AllModuleNames()...)) + return o +} + +func (o *testopts) Module(name string, mod interface{}) *testopts { + c := o.copy() + switch mod := mod.(type) { + case tengo.Importable: + c.modules.Add(name, mod) + case string: + c.modules.AddSourceModule(name, []byte(mod)) + case []byte: + c.modules.AddSourceModule(name, mod) + default: + panic(fmt.Errorf("invalid module type: %T", mod)) + } + return c +} + +func (o *testopts) Symbol(name string, value tengo.Object) *testopts { + c := o.copy() + c.symbols[name] = value + return c +} + +func (o *testopts) MaxAllocs(limit int64) *testopts { + c := o.copy() + c.maxAllocs = limit + return c +} + +func (o *testopts) Skip2ndPass() *testopts { + c := o.copy() + c.skip2ndPass = true + return c +} + +func TestArray(t *testing.T) { + expectRun(t, `out = [1, 2 * 2, 3 + 3]`, nil, ARR{1, 4, 6}) + + // array copy-by-reference + expectRun(t, `a1 := [1, 2, 3]; a2 := a1; a1[0] = 5; out = a2`, + nil, ARR{5, 2, 3}) + expectRun(t, `func () { a1 := [1, 2, 3]; a2 := a1; a1[0] = 5; out = a2 }()`, + nil, ARR{5, 2, 3}) + + // array index set + expectError(t, `a1 := [1, 2, 3]; a1[3] = 5`, + nil, "index out of bounds") + + // index operator + arr := ARR{1, 2, 3, 4, 5, 6} + arrStr := `[1, 2, 3, 4, 5, 6]` + arrLen := 6 + for idx := 0; idx < arrLen; idx++ { + expectRun(t, fmt.Sprintf("out = %s[%d]", arrStr, idx), + nil, arr[idx]) + expectRun(t, fmt.Sprintf("out = %s[0 + %d]", arrStr, idx), + nil, arr[idx]) + expectRun(t, fmt.Sprintf("out = %s[1 + %d - 1]", arrStr, idx), + nil, arr[idx]) + expectRun(t, fmt.Sprintf("idx := %d; out = %s[idx]", idx, arrStr), + nil, arr[idx]) + } + + expectRun(t, fmt.Sprintf("%s[%d]", arrStr, -1), + nil, tengo.UndefinedValue) + expectRun(t, fmt.Sprintf("%s[%d]", arrStr, arrLen), + nil, tengo.UndefinedValue) + + // slice operator + for low := 0; low < arrLen; low++ { + expectRun(t, fmt.Sprintf("out = %s[%d:%d]", arrStr, low, low), + nil, ARR{}) + for high := low; high <= arrLen; high++ { + expectRun(t, fmt.Sprintf("out = %s[%d:%d]", arrStr, low, high), + nil, arr[low:high]) + expectRun(t, fmt.Sprintf("out = %s[0 + %d : 0 + %d]", + arrStr, low, high), nil, arr[low:high]) + expectRun(t, fmt.Sprintf("out = %s[1 + %d - 1 : 1 + %d - 1]", + arrStr, low, high), nil, arr[low:high]) + expectRun(t, fmt.Sprintf("out = %s[:%d]", arrStr, high), + nil, arr[:high]) + expectRun(t, fmt.Sprintf("out = %s[%d:]", arrStr, low), + nil, arr[low:]) + } + } + + expectRun(t, fmt.Sprintf("out = %s[:]", arrStr), + nil, arr) + expectRun(t, fmt.Sprintf("out = %s[%d:]", arrStr, -1), + nil, arr) + expectRun(t, fmt.Sprintf("out = %s[:%d]", arrStr, arrLen+1), + nil, arr) + expectRun(t, fmt.Sprintf("out = %s[%d:%d]", arrStr, 2, 2), + nil, ARR{}) + + expectError(t, fmt.Sprintf("%s[:%d]", arrStr, -1), + nil, "invalid slice index") + expectError(t, fmt.Sprintf("%s[%d:]", arrStr, arrLen+1), + nil, "invalid slice index") + expectError(t, fmt.Sprintf("%s[%d:%d]", arrStr, 0, -1), + nil, "invalid slice index") + expectError(t, fmt.Sprintf("%s[%d:%d]", arrStr, 2, 1), + nil, "invalid slice index") +} + +func TestAssignment(t *testing.T) { + expectRun(t, `a := 1; a = 2; out = a`, nil, 2) + expectRun(t, `a := 1; a = 2; out = a`, nil, 2) + expectRun(t, `a := 1; a = a + 4; out = a`, nil, 5) + expectRun(t, `a := 1; f1 := func() { a = 2; return a }; out = f1()`, + nil, 2) + expectRun(t, `a := 1; f1 := func() { a := 3; a = 2; return a }; out = f1()`, + nil, 2) + + expectRun(t, `a := 1; out = a`, nil, 1) + expectRun(t, `a := 1; a = 2; out = a`, nil, 2) + expectRun(t, `a := 1; func() { a = 2 }(); out = a`, nil, 2) + expectRun(t, `a := 1; func() { a := 2 }(); out = a`, nil, 1) // "a := 2" defines a new local variable 'a' + expectRun(t, `a := 1; func() { b := 2; out = b }()`, nil, 2) + expectRun(t, ` +out = func() { + a := 2 + func() { + a = 3 // captured from outer scope + }() + return a +}() +`, nil, 3) + + expectRun(t, ` +func() { + a := 5 + out = func() { + a := 4 + return a + }() +}()`, nil, 4) + + expectError(t, `a := 1; a := 2`, nil, "redeclared") // redeclared in the same scope + expectError(t, `func() { a := 1; a := 2 }()`, nil, "redeclared") // redeclared in the same scope + + expectRun(t, `a := 1; a += 2; out = a`, nil, 3) + expectRun(t, `a := 1; a += 4 - 2;; out = a`, nil, 3) + expectRun(t, `a := 3; a -= 1;; out = a`, nil, 2) + expectRun(t, `a := 3; a -= 5 - 4;; out = a`, nil, 2) + expectRun(t, `a := 2; a *= 4;; out = a`, nil, 8) + expectRun(t, `a := 2; a *= 1 + 3;; out = a`, nil, 8) + expectRun(t, `a := 10; a /= 2;; out = a`, nil, 5) + expectRun(t, `a := 10; a /= 5 - 3;; out = a`, nil, 5) + + // compound assignment operator does not define new variable + expectError(t, `a += 4`, nil, "unresolved reference") + expectError(t, `a -= 4`, nil, "unresolved reference") + expectError(t, `a *= 4`, nil, "unresolved reference") + expectError(t, `a /= 4`, nil, "unresolved reference") + + expectRun(t, ` +f1 := func() { + f2 := func() { + a := 1 + a += 2 // it's a statement, not an expression + return a + }; + + return f2(); +}; + +out = f1();`, nil, 3) + expectRun(t, `f1 := func() { f2 := func() { a := 1; a += 4 - 2; return a }; return f2(); }; out = f1()`, + nil, 3) + expectRun(t, `f1 := func() { f2 := func() { a := 3; a -= 1; return a }; return f2(); }; out = f1()`, + nil, 2) + expectRun(t, `f1 := func() { f2 := func() { a := 3; a -= 5 - 4; return a }; return f2(); }; out = f1()`, + nil, 2) + expectRun(t, `f1 := func() { f2 := func() { a := 2; a *= 4; return a }; return f2(); }; out = f1()`, + nil, 8) + expectRun(t, `f1 := func() { f2 := func() { a := 2; a *= 1 + 3; return a }; return f2(); }; out = f1()`, + nil, 8) + expectRun(t, `f1 := func() { f2 := func() { a := 10; a /= 2; return a }; return f2(); }; out = f1()`, + nil, 5) + expectRun(t, `f1 := func() { f2 := func() { a := 10; a /= 5 - 3; return a }; return f2(); }; out = f1()`, + nil, 5) + + expectRun(t, `a := 1; f1 := func() { f2 := func() { a += 2; return a }; return f2(); }; out = f1()`, + nil, 3) + + expectRun(t, ` + f1 := func(a) { + return func(b) { + c := a + c += b * 2 + return c + } + } + + out = f1(3)(4) + `, nil, 11) + + expectRun(t, ` + out = func() { + a := 1 + func() { + a = 2 + func() { + a = 3 + func() { + a := 4 // declared new + }() + }() + }() + return a + }() + `, nil, 3) + + // write on free variables + expectRun(t, ` + f1 := func() { + a := 5 + + return func() { + a += 3 + return a + }() + } + out = f1() + `, nil, 8) + + expectRun(t, ` + out = func() { + f1 := func() { + a := 5 + add1 := func() { a += 1 } + add2 := func() { a += 2 } + a += 3 + return func() { a += 4; add1(); add2(); a += 5; return a } + } + return f1() + }()() + `, nil, 20) + + expectRun(t, ` + it := func(seq, fn) { + fn(seq[0]) + fn(seq[1]) + fn(seq[2]) + } + + foo := func(a) { + b := 0 + it([1, 2, 3], func(x) { + b = x + a + }) + return b + } + + out = foo(2) + `, nil, 5) + + expectRun(t, ` + it := func(seq, fn) { + fn(seq[0]) + fn(seq[1]) + fn(seq[2]) + } + + foo := func(a) { + b := 0 + it([1, 2, 3], func(x) { + b += x + a + }) + return b + } + + out = foo(2) + `, nil, 12) + + expectRun(t, ` +out = func() { + a := 1 + func() { + a = 2 + }() + return a +}() +`, nil, 2) + + expectRun(t, ` +f := func() { + a := 1 + return { + b: func() { a += 3 }, + c: func() { a += 2 }, + d: func() { return a } + } +} +m := f() +m.b() +m.c() +out = m.d() +`, nil, 6) + + expectRun(t, ` +each := func(s, x) { for i:=0; i> 2`, nil, 4) + + expectRun(t, `out = 1; out &= 1`, nil, 1) + expectRun(t, `out = 1; out |= 0`, nil, 1) + expectRun(t, `out = 1; out ^= 0`, nil, 1) + expectRun(t, `out = 1; out &^= 0`, nil, 1) + expectRun(t, `out = 1; out <<= 2`, nil, 4) + expectRun(t, `out = 16; out >>= 2`, nil, 4) + + expectRun(t, `out = ^0`, nil, ^0) + expectRun(t, `out = ^1`, nil, ^1) + expectRun(t, `out = ^55`, nil, ^55) + expectRun(t, `out = ^-55`, nil, ^-55) +} + +func TestBoolean(t *testing.T) { + expectRun(t, `out = true`, nil, true) + expectRun(t, `out = false`, nil, false) + + expectRun(t, `out = 1 < 2`, nil, true) + expectRun(t, `out = 1 > 2`, nil, false) + expectRun(t, `out = 1 < 1`, nil, false) + expectRun(t, `out = 1 > 2`, nil, false) + expectRun(t, `out = 1 == 1`, nil, true) + expectRun(t, `out = 1 != 1`, nil, false) + expectRun(t, `out = 1 == 2`, nil, false) + expectRun(t, `out = 1 != 2`, nil, true) + expectRun(t, `out = 1 <= 2`, nil, true) + expectRun(t, `out = 1 >= 2`, nil, false) + expectRun(t, `out = 1 <= 1`, nil, true) + expectRun(t, `out = 1 >= 2`, nil, false) + + expectRun(t, `out = true == true`, nil, true) + expectRun(t, `out = false == false`, nil, true) + expectRun(t, `out = true == false`, nil, false) + expectRun(t, `out = true != false`, nil, true) + expectRun(t, `out = false != true`, nil, true) + expectRun(t, `out = (1 < 2) == true`, nil, true) + expectRun(t, `out = (1 < 2) == false`, nil, false) + expectRun(t, `out = (1 > 2) == true`, nil, false) + expectRun(t, `out = (1 > 2) == false`, nil, true) + + expectError(t, `5 + true`, nil, "invalid operation") + expectError(t, `5 + true; 5`, nil, "invalid operation") + expectError(t, `-true`, nil, "invalid operation") + expectError(t, `true + false`, nil, "invalid operation") + expectError(t, `5; true + false; 5`, nil, "invalid operation") + expectError(t, `if (10 > 1) { true + false; }`, nil, "invalid operation") + expectError(t, ` +func() { + if (10 > 1) { + if (10 > 1) { + return true + false; + } + + return 1; + } +}() +`, nil, "invalid operation") + expectError(t, `if (true + false) { 10 }`, nil, "invalid operation") + expectError(t, `10 + (true + false)`, nil, "invalid operation") + expectError(t, `(true + false) + 20`, nil, "invalid operation") + expectError(t, `!(true + false)`, nil, "invalid operation") +} + +func TestUndefined(t *testing.T) { + expectRun(t, `out = undefined`, nil, tengo.UndefinedValue) + expectRun(t, `out = undefined.a`, nil, tengo.UndefinedValue) + expectRun(t, `out = undefined[1]`, nil, tengo.UndefinedValue) + expectRun(t, `out = undefined.a.b`, nil, tengo.UndefinedValue) + expectRun(t, `out = undefined[1][2]`, nil, tengo.UndefinedValue) + expectRun(t, `out = undefined ? 1 : 2`, nil, 2) + expectRun(t, `out = undefined == undefined`, nil, true) + expectRun(t, `out = undefined == 1`, nil, false) + expectRun(t, `out = 1 == undefined`, nil, false) + expectRun(t, `out = undefined == float([])`, nil, true) + expectRun(t, `out = float([]) == undefined`, nil, true) +} + +func TestBuiltinFunction(t *testing.T) { + expectRun(t, `out = len("")`, nil, 0) + expectRun(t, `out = len("four")`, nil, 4) + expectRun(t, `out = len("hello world")`, nil, 11) + expectRun(t, `out = len([])`, nil, 0) + expectRun(t, `out = len([1, 2, 3])`, nil, 3) + expectRun(t, `out = len({})`, nil, 0) + expectRun(t, `out = len({a:1, b:2})`, nil, 2) + expectRun(t, `out = len(immutable([]))`, nil, 0) + expectRun(t, `out = len(immutable([1, 2, 3]))`, nil, 3) + expectRun(t, `out = len(immutable({}))`, nil, 0) + expectRun(t, `out = len(immutable({a:1, b:2}))`, nil, 2) + expectError(t, `len(1)`, nil, "invalid type for argument") + expectError(t, `len("one", "two")`, nil, "wrong number of arguments") + + expectRun(t, `out = copy(1)`, nil, 1) + expectError(t, `copy(1, 2)`, nil, "wrong number of arguments") + + expectRun(t, `out = append([1, 2, 3], 4)`, nil, ARR{1, 2, 3, 4}) + expectRun(t, `out = append([1, 2, 3], 4, 5, 6)`, nil, ARR{1, 2, 3, 4, 5, 6}) + expectRun(t, `out = append([1, 2, 3], "foo", false)`, + nil, ARR{1, 2, 3, "foo", false}) + + expectRun(t, `out = int(1)`, nil, 1) + expectRun(t, `out = int(1.8)`, nil, 1) + expectRun(t, `out = int("-522")`, nil, -522) + expectRun(t, `out = int(true)`, nil, 1) + expectRun(t, `out = int(false)`, nil, 0) + expectRun(t, `out = int('8')`, nil, 56) + expectRun(t, `out = int([1])`, nil, tengo.UndefinedValue) + expectRun(t, `out = int({a: 1})`, nil, tengo.UndefinedValue) + expectRun(t, `out = int(undefined)`, nil, tengo.UndefinedValue) + expectRun(t, `out = int("-522", 1)`, nil, -522) + expectRun(t, `out = int(undefined, 1)`, nil, 1) + expectRun(t, `out = int(undefined, 1.8)`, nil, 1.8) + expectRun(t, `out = int(undefined, string(1))`, nil, "1") + expectRun(t, `out = int(undefined, undefined)`, nil, tengo.UndefinedValue) + + expectRun(t, `out = string(1)`, nil, "1") + expectRun(t, `out = string(1.8)`, nil, "1.8") + expectRun(t, `out = string("-522")`, nil, "-522") + expectRun(t, `out = string(true)`, nil, "true") + expectRun(t, `out = string(false)`, nil, "false") + expectRun(t, `out = string('8')`, nil, "8") + expectRun(t, `out = string([1,8.1,true,3])`, nil, "[1, 8.1, true, 3]") + expectRun(t, `out = string({b: "foo"})`, nil, `{b: "foo"}`) + expectRun(t, `out = string(undefined)`, nil, tengo.UndefinedValue) // not "undefined" + expectRun(t, `out = string(1, "-522")`, nil, "1") + expectRun(t, `out = string(undefined, "-522")`, nil, "-522") // not "undefined" + + expectRun(t, `out = float(1)`, nil, 1.0) + expectRun(t, `out = float(1.8)`, nil, 1.8) + expectRun(t, `out = float("-52.2")`, nil, -52.2) + expectRun(t, `out = float(true)`, nil, tengo.UndefinedValue) + expectRun(t, `out = float(false)`, nil, tengo.UndefinedValue) + expectRun(t, `out = float('8')`, nil, tengo.UndefinedValue) + expectRun(t, `out = float([1,8.1,true,3])`, nil, tengo.UndefinedValue) + expectRun(t, `out = float({a: 1, b: "foo"})`, nil, tengo.UndefinedValue) + expectRun(t, `out = float(undefined)`, nil, tengo.UndefinedValue) + expectRun(t, `out = float("-52.2", 1.8)`, nil, -52.2) + expectRun(t, `out = float(undefined, 1)`, nil, 1) + expectRun(t, `out = float(undefined, 1.8)`, nil, 1.8) + expectRun(t, `out = float(undefined, "-52.2")`, nil, "-52.2") + expectRun(t, `out = float(undefined, char(56))`, nil, '8') + expectRun(t, `out = float(undefined, undefined)`, nil, tengo.UndefinedValue) + + expectRun(t, `out = char(56)`, nil, '8') + expectRun(t, `out = char(1.8)`, nil, tengo.UndefinedValue) + expectRun(t, `out = char("-52.2")`, nil, tengo.UndefinedValue) + expectRun(t, `out = char(true)`, nil, tengo.UndefinedValue) + expectRun(t, `out = char(false)`, nil, tengo.UndefinedValue) + expectRun(t, `out = char('8')`, nil, '8') + expectRun(t, `out = char([1,8.1,true,3])`, nil, tengo.UndefinedValue) + expectRun(t, `out = char({a: 1, b: "foo"})`, nil, tengo.UndefinedValue) + expectRun(t, `out = char(undefined)`, nil, tengo.UndefinedValue) + expectRun(t, `out = char(56, 'a')`, nil, '8') + expectRun(t, `out = char(undefined, '8')`, nil, '8') + expectRun(t, `out = char(undefined, 56)`, nil, 56) + expectRun(t, `out = char(undefined, "-52.2")`, nil, "-52.2") + expectRun(t, `out = char(undefined, undefined)`, nil, tengo.UndefinedValue) + + expectRun(t, `out = bool(1)`, nil, true) // non-zero integer: true + expectRun(t, `out = bool(0)`, nil, false) // zero: true + expectRun(t, `out = bool(1.8)`, nil, true) // all floats (except for NaN): true + expectRun(t, `out = bool(0.0)`, nil, true) // all floats (except for NaN): true + expectRun(t, `out = bool("false")`, nil, true) // non-empty string: true + expectRun(t, `out = bool("")`, nil, false) // empty string: false + expectRun(t, `out = bool(true)`, nil, true) // true: true + expectRun(t, `out = bool(false)`, nil, false) // false: false + expectRun(t, `out = bool('8')`, nil, true) // non-zero chars: true + expectRun(t, `out = bool(char(0))`, nil, false) // zero char: false + expectRun(t, `out = bool([1])`, nil, true) // non-empty arrays: true + expectRun(t, `out = bool([])`, nil, false) // empty array: false + expectRun(t, `out = bool({a: 1})`, nil, true) // non-empty maps: true + expectRun(t, `out = bool({})`, nil, false) // empty maps: false + expectRun(t, `out = bool(undefined)`, nil, false) // undefined: false + + expectRun(t, `out = bytes(1)`, nil, []byte{0}) + expectRun(t, `out = bytes(1.8)`, nil, tengo.UndefinedValue) + expectRun(t, `out = bytes("-522")`, nil, []byte{'-', '5', '2', '2'}) + expectRun(t, `out = bytes(true)`, nil, tengo.UndefinedValue) + expectRun(t, `out = bytes(false)`, nil, tengo.UndefinedValue) + expectRun(t, `out = bytes('8')`, nil, tengo.UndefinedValue) + expectRun(t, `out = bytes([1])`, nil, tengo.UndefinedValue) + expectRun(t, `out = bytes({a: 1})`, nil, tengo.UndefinedValue) + expectRun(t, `out = bytes(undefined)`, nil, tengo.UndefinedValue) + expectRun(t, `out = bytes("-522", ['8'])`, nil, []byte{'-', '5', '2', '2'}) + expectRun(t, `out = bytes(undefined, "-522")`, nil, "-522") + expectRun(t, `out = bytes(undefined, 1)`, nil, 1) + expectRun(t, `out = bytes(undefined, 1.8)`, nil, 1.8) + expectRun(t, `out = bytes(undefined, int("-522"))`, nil, -522) + expectRun(t, `out = bytes(undefined, undefined)`, nil, tengo.UndefinedValue) + + expectRun(t, `out = is_error(error(1))`, nil, true) + expectRun(t, `out = is_error(1)`, nil, false) + + expectRun(t, `out = is_undefined(undefined)`, nil, true) + expectRun(t, `out = is_undefined(error(1))`, nil, false) + + // type_name + expectRun(t, `out = type_name(1)`, nil, "int") + expectRun(t, `out = type_name(1.1)`, nil, "float") + expectRun(t, `out = type_name("a")`, nil, "string") + expectRun(t, `out = type_name([1,2,3])`, nil, "array") + expectRun(t, `out = type_name({k:1})`, nil, "map") + expectRun(t, `out = type_name('a')`, nil, "char") + expectRun(t, `out = type_name(true)`, nil, "bool") + expectRun(t, `out = type_name(false)`, nil, "bool") + expectRun(t, `out = type_name(bytes( 1))`, nil, "bytes") + expectRun(t, `out = type_name(undefined)`, nil, "undefined") + expectRun(t, `out = type_name(error("err"))`, nil, "error") + expectRun(t, `out = type_name(func() {})`, nil, "compiled-function") + expectRun(t, `a := func(x) { return func() { return x } }; out = type_name(a(5))`, + nil, "compiled-function") // closure + + // is_function + expectRun(t, `out = is_function(1)`, nil, false) + expectRun(t, `out = is_function(func() {})`, nil, true) + expectRun(t, `out = is_function(func(x) { return x })`, nil, true) + expectRun(t, `out = is_function(len)`, nil, false) // builtin function + expectRun(t, `a := func(x) { return func() { return x } }; out = is_function(a)`, + nil, true) // function + expectRun(t, `a := func(x) { return func() { return x } }; out = is_function(a(5))`, + nil, true) // closure + expectRun(t, `out = is_function(x)`, + Opts().Symbol("x", &StringArray{ + Value: []string{"foo", "bar"}, + }).Skip2ndPass(), + false) // user object + + // is_callable + expectRun(t, `out = is_callable(1)`, nil, false) + expectRun(t, `out = is_callable(func() {})`, nil, true) + expectRun(t, `out = is_callable(func(x) { return x })`, nil, true) + expectRun(t, `out = is_callable(len)`, nil, true) // builtin function + expectRun(t, `a := func(x) { return func() { return x } }; out = is_callable(a)`, + nil, true) // function + expectRun(t, `a := func(x) { return func() { return x } }; out = is_callable(a(5))`, + nil, true) // closure + expectRun(t, `out = is_callable(x)`, + Opts().Symbol("x", &StringArray{ + Value: []string{"foo", "bar"}, + }).Skip2ndPass(), true) // user object + + expectRun(t, `out = format("")`, nil, "") + expectRun(t, `out = format("foo")`, nil, "foo") + expectRun(t, `out = format("foo %d %v %s", 1, 2, "bar")`, + nil, "foo 1 2 bar") + expectRun(t, `out = format("foo %v", [1, "bar", true])`, + nil, `foo [1, "bar", true]`) + expectRun(t, `out = format("foo %v %d", [1, "bar", true], 19)`, + nil, `foo [1, "bar", true] 19`) + expectRun(t, `out = format("foo %v", {"a": {"b": {"c": [1, 2, 3]}}})`, + nil, `foo {a: {b: {c: [1, 2, 3]}}}`) + expectRun(t, `out = format("%v", [1, [2, [3, 4]]])`, + nil, `[1, [2, [3, 4]]]`) + + tengo.MaxStringLen = 9 + expectError(t, `format("%s", "1234567890")`, + nil, "exceeding string size limit") + tengo.MaxStringLen = 2147483647 +} + +func TestBytesN(t *testing.T) { + curMaxBytesLen := tengo.MaxBytesLen + defer func() { tengo.MaxBytesLen = curMaxBytesLen }() + tengo.MaxBytesLen = 10 + + expectRun(t, `out = bytes(0)`, nil, make([]byte, 0)) + expectRun(t, `out = bytes(10)`, nil, make([]byte, 10)) + expectError(t, `bytes(11)`, nil, "bytes size limit") + + tengo.MaxBytesLen = 1000 + expectRun(t, `out = bytes(1000)`, nil, make([]byte, 1000)) + expectError(t, `bytes(1001)`, nil, "bytes size limit") +} + +func TestBytes(t *testing.T) { + expectRun(t, `out = bytes("Hello World!")`, nil, []byte("Hello World!")) + expectRun(t, `out = bytes("Hello") + bytes(" ") + bytes("World!")`, + nil, []byte("Hello World!")) + + // bytes[] -> int + expectRun(t, `out = bytes("abcde")[0]`, nil, 97) + expectRun(t, `out = bytes("abcde")[1]`, nil, 98) + expectRun(t, `out = bytes("abcde")[4]`, nil, 101) + expectRun(t, `out = bytes("abcde")[10]`, nil, tengo.UndefinedValue) +} + +func TestCall(t *testing.T) { + expectRun(t, `a := { b: func(x) { return x + 2 } }; out = a.b(5)`, + nil, 7) + expectRun(t, `a := { b: { c: func(x) { return x + 2 } } }; out = a.b.c(5)`, + nil, 7) + expectRun(t, `a := { b: { c: func(x) { return x + 2 } } }; out = a["b"].c(5)`, + nil, 7) + expectError(t, `a := 1 +b := func(a, c) { + c(a) +} + +c := func(a) { + a() +} +b(a, c) +`, nil, "Runtime Error: not callable: int\n\tat test:7:4\n\tat test:3:4\n\tat test:9:1") +} + +func TestChar(t *testing.T) { + expectRun(t, `out = 'a'`, nil, 'a') + expectRun(t, `out = '九'`, nil, rune(20061)) + expectRun(t, `out = 'Æ'`, nil, rune(198)) + + expectRun(t, `out = '0' + '9'`, nil, rune(105)) + expectRun(t, `out = '0' + 9`, nil, '9') + expectRun(t, `out = '9' - 4`, nil, '5') + expectRun(t, `out = '0' == '0'`, nil, true) + expectRun(t, `out = '0' != '0'`, nil, false) + expectRun(t, `out = '2' < '4'`, nil, true) + expectRun(t, `out = '2' > '4'`, nil, false) + expectRun(t, `out = '2' <= '4'`, nil, true) + expectRun(t, `out = '2' >= '4'`, nil, false) + expectRun(t, `out = '4' < '4'`, nil, false) + expectRun(t, `out = '4' > '4'`, nil, false) + expectRun(t, `out = '4' <= '4'`, nil, true) + expectRun(t, `out = '4' >= '4'`, nil, true) +} + +func TestCondExpr(t *testing.T) { + expectRun(t, `out = true ? 5 : 10`, nil, 5) + expectRun(t, `out = false ? 5 : 10`, nil, 10) + expectRun(t, `out = (1 == 1) ? 2 + 3 : 12 - 2`, nil, 5) + expectRun(t, `out = (1 != 1) ? 2 + 3 : 12 - 2`, nil, 10) + expectRun(t, `out = (1 == 1) ? true ? 10 - 8 : 1 + 3 : 12 - 2`, nil, 2) + expectRun(t, `out = (1 == 1) ? false ? 10 - 8 : 1 + 3 : 12 - 2`, nil, 4) + + expectRun(t, ` +out = 0 +f1 := func() { out += 10 } +f2 := func() { out = -out } +true ? f1() : f2() +`, nil, 10) + expectRun(t, ` +out = 5 +f1 := func() { out += 10 } +f2 := func() { out = -out } +false ? f1() : f2() +`, nil, -5) + expectRun(t, ` +f1 := func(a) { return a + 2 } +f2 := func(a) { return a - 2 } +f3 := func(a) { return a + 10 } +f4 := func(a) { return -a } + +f := func(c) { + return c == 0 ? f1(c) : f2(c) ? f3(c) : f4(c) +} + +out = [f(0), f(1), f(2)] +`, nil, ARR{2, 11, -2}) + + expectRun(t, `f := func(a) { return -a }; out = f(true ? 5 : 3)`, nil, -5) + expectRun(t, `out = [false?5:10, true?1:2]`, nil, ARR{10, 1}) + + expectRun(t, ` +out = 1 > 2 ? + 1 + 2 + 3 : + 10 - 5`, nil, 5) +} + +func TestEquality(t *testing.T) { + testEquality(t, `1`, `1`, true) + testEquality(t, `1`, `2`, false) + + testEquality(t, `1.0`, `1.0`, true) + testEquality(t, `1.0`, `1.1`, false) + + testEquality(t, `true`, `true`, true) + testEquality(t, `true`, `false`, false) + + testEquality(t, `"foo"`, `"foo"`, true) + testEquality(t, `"foo"`, `"bar"`, false) + + testEquality(t, `'f'`, `'f'`, true) + testEquality(t, `'f'`, `'b'`, false) + + testEquality(t, `[]`, `[]`, true) + testEquality(t, `[1]`, `[1]`, true) + testEquality(t, `[1]`, `[1, 2]`, false) + testEquality(t, `["foo", "bar"]`, `["foo", "bar"]`, true) + testEquality(t, `["foo", "bar"]`, `["bar", "foo"]`, false) + + testEquality(t, `{}`, `{}`, true) + testEquality(t, `{a: 1, b: 2}`, `{b: 2, a: 1}`, true) + testEquality(t, `{a: 1, b: 2}`, `{b: 2}`, false) + testEquality(t, `{a: 1, b: {}}`, `{b: {}, a: 1}`, true) + + testEquality(t, `1`, `"foo"`, false) + testEquality(t, `1`, `true`, false) + testEquality(t, `[1]`, `["1"]`, false) + testEquality(t, `[1, [2]]`, `[1, ["2"]]`, false) + testEquality(t, `{a: 1}`, `{a: "1"}`, false) + testEquality(t, `{a: 1, b: {c: 2}}`, `{a: 1, b: {c: "2"}}`, false) +} + +func testEquality(t *testing.T, lhs, rhs string, expected bool) { + // 1. equality is commutative + // 2. equality and inequality must be always opposite + expectRun(t, fmt.Sprintf("out = %s == %s", lhs, rhs), nil, expected) + expectRun(t, fmt.Sprintf("out = %s == %s", rhs, lhs), nil, expected) + expectRun(t, fmt.Sprintf("out = %s != %s", lhs, rhs), nil, !expected) + expectRun(t, fmt.Sprintf("out = %s != %s", rhs, lhs), nil, !expected) +} + +func TestVMErrorInfo(t *testing.T) { + expectError(t, `a := 5 +a + "boo"`, + nil, "Runtime Error: invalid operation: int + string\n\tat test:2:1") + + expectError(t, `a := 5 +b := a(5)`, + nil, "Runtime Error: not callable: int\n\tat test:2:6") + + expectError(t, `a := 5 +b := {} +b.x.y = 10`, + nil, "Runtime Error: not index-assignable: undefined\n\tat test:3:1") + + expectError(t, ` +a := func() { + b := 5 + b += "foo" +} +a()`, + nil, "Runtime Error: invalid operation: int + string\n\tat test:4:2") + + expectError(t, `a := 5 +a + import("mod1")`, Opts().Module( + "mod1", `export "foo"`, + ), ": invalid operation: int + string\n\tat test:2:1") + + expectError(t, `a := import("mod1")()`, + Opts().Module( + "mod1", ` +export func() { + b := 5 + return b + "foo" +}`), "Runtime Error: invalid operation: int + string\n\tat mod1:4:9") + + expectError(t, `a := import("mod1")()`, + Opts().Module( + "mod1", `export import("mod2")()`). + Module( + "mod2", ` +export func() { + b := 5 + return b + "foo" +}`), "Runtime Error: invalid operation: int + string\n\tat mod2:4:9") +} + +func TestError(t *testing.T) { + expectRun(t, `out = error(1)`, nil, errorObject(1)) + expectRun(t, `out = error(1).value`, nil, 1) + expectRun(t, `out = error("some error")`, nil, errorObject("some error")) + expectRun(t, `out = error("some" + " error")`, nil, errorObject("some error")) + expectRun(t, `out = func() { return error(5) }()`, nil, errorObject(5)) + expectRun(t, `out = error(error("foo"))`, nil, errorObject(errorObject("foo"))) + expectRun(t, `out = error("some error")`, nil, errorObject("some error")) + expectRun(t, `out = error("some error").value`, nil, "some error") + expectRun(t, `out = error("some error")["value"]`, nil, "some error") + + expectError(t, `error("error").err`, nil, "invalid index on error") + expectError(t, `error("error").value_`, nil, "invalid index on error") + expectError(t, `error([1,2,3])[1]`, nil, "invalid index on error") +} + +func TestFloat(t *testing.T) { + expectRun(t, `out = 0.0`, nil, 0.0) + expectRun(t, `out = -10.3`, nil, -10.3) + expectRun(t, `out = 3.2 + 2.0 * -4.0`, nil, -4.8) + expectRun(t, `out = 4 + 2.3`, nil, 6.3) + expectRun(t, `out = 2.3 + 4`, nil, 6.3) + expectRun(t, `out = +5.0`, nil, 5.0) + expectRun(t, `out = -5.0 + +5.0`, nil, 0.0) +} + +func TestForIn(t *testing.T) { + // array + expectRun(t, `out = 0; for x in [1, 2, 3] { out += x }`, + nil, 6) // value + expectRun(t, `out = 0; for i, x in [1, 2, 3] { out += i + x }`, + nil, 9) // index, value + expectRun(t, `out = 0; func() { for i, x in [1, 2, 3] { out += i + x } }()`, + nil, 9) // index, value + expectRun(t, `out = 0; for i, _ in [1, 2, 3] { out += i }`, + nil, 3) // index, _ + expectRun(t, `out = 0; func() { for i, _ in [1, 2, 3] { out += i } }()`, + nil, 3) // index, _ + + // map + expectRun(t, `out = 0; for v in {a:2,b:3,c:4} { out += v }`, + nil, 9) // value + expectRun(t, `out = ""; for k, v in {a:2,b:3,c:4} { out = k; if v==3 { break } }`, + nil, "b") // key, value + expectRun(t, `out = ""; for k, _ in {a:2} { out += k }`, + nil, "a") // key, _ + expectRun(t, `out = 0; for _, v in {a:2,b:3,c:4} { out += v }`, + nil, 9) // _, value + expectRun(t, `out = ""; func() { for k, v in {a:2,b:3,c:4} { out = k; if v==3 { break } } }()`, + nil, "b") // key, value + + // string + expectRun(t, `out = ""; for c in "abcde" { out += c }`, + nil, "abcde") + expectRun(t, `out = ""; for i, c in "abcde" { if i == 2 { continue }; out += c }`, + nil, "abde") +} + +func TestFor(t *testing.T) { + expectRun(t, ` + out = 0 + for { + out++ + if out == 5 { + break + } + }`, nil, 5) + + expectRun(t, ` + out = 0 + for { + out++ + if out == 5 { + break + } + }`, nil, 5) + + expectRun(t, ` + out = 0 + a := 0 + for { + a++ + if a == 3 { continue } + if a == 5 { break } + out += a + }`, nil, 7) // 1 + 2 + 4 + + expectRun(t, ` + out = 0 + a := 0 + for { + a++ + if a == 3 { continue } + out += a + if a == 5 { break } + }`, nil, 12) // 1 + 2 + 4 + 5 + + expectRun(t, ` + out = 0 + for true { + out++ + if out == 5 { + break + } + }`, nil, 5) + + expectRun(t, ` + a := 0 + for true { + a++ + if a == 5 { + break + } + } + out = a`, nil, 5) + + expectRun(t, ` + out = 0 + a := 0 + for true { + a++ + if a == 3 { continue } + if a == 5 { break } + out += a + }`, nil, 7) // 1 + 2 + 4 + + expectRun(t, ` + out = 0 + a := 0 + for true { + a++ + if a == 3 { continue } + out += a + if a == 5 { break } + }`, nil, 12) // 1 + 2 + 4 + 5 + + expectRun(t, ` + out = 0 + func() { + for true { + out++ + if out == 5 { + return + } + } + }()`, nil, 5) + + expectRun(t, ` + out = 0 + for a:=1; a<=10; a++ { + out += a + }`, nil, 55) + + expectRun(t, ` + out = 0 + for a:=1; a<=3; a++ { + for b:=3; b<=6; b++ { + out += b + } + }`, nil, 54) + + expectRun(t, ` + out = 0 + func() { + for { + out++ + if out == 5 { + break + } + } + }()`, nil, 5) + + expectRun(t, ` + out = 0 + func() { + for true { + out++ + if out == 5 { + break + } + } + }()`, nil, 5) + + expectRun(t, ` + out = func() { + a := 0 + for { + a++ + if a == 5 { + break + } + } + return a + }()`, nil, 5) + + expectRun(t, ` + out = func() { + a := 0 + for true { + a++ + if a== 5 { + break + } + } + return a + }()`, nil, 5) + + expectRun(t, ` + out = func() { + a := 0 + func() { + for { + a++ + if a == 5 { + break + } + } + }() + return a + }()`, nil, 5) + + expectRun(t, ` + out = func() { + a := 0 + func() { + for true { + a++ + if a == 5 { + break + } + } + }() + return a + }()`, nil, 5) + + expectRun(t, ` + out = func() { + sum := 0 + for a:=1; a<=10; a++ { + sum += a + } + return sum + }()`, nil, 55) + + expectRun(t, ` + out = func() { + sum := 0 + for a:=1; a<=4; a++ { + for b:=3; b<=5; b++ { + sum += b + } + } + return sum + }()`, nil, 48) // (3+4+5) * 4 + + expectRun(t, ` + a := 1 + for ; a<=10; a++ { + if a == 5 { + break + } + } + out = a`, nil, 5) + + expectRun(t, ` + out = 0 + for a:=1; a<=10; a++ { + if a == 3 { + continue + } + out += a + if a == 5 { + break + } + }`, nil, 12) // 1 + 2 + 4 + 5 + + expectRun(t, ` + out = 0 + for a:=1; a<=10; { + if a == 3 { + a++ + continue + } + out += a + if a == 5 { + break + } + a++ + }`, nil, 12) // 1 + 2 + 4 + 5 +} + +func TestFunction(t *testing.T) { + // function with no "return" statement returns "invalid" value. + expectRun(t, `f1 := func() {}; out = f1();`, + nil, tengo.UndefinedValue) + expectRun(t, `f1 := func() {}; f2 := func() { return f1(); }; f1(); out = f2();`, + nil, tengo.UndefinedValue) + expectRun(t, `f := func(x) { x; }; out = f(5);`, + nil, tengo.UndefinedValue) + + expectRun(t, `f := func(...x) { return x; }; out = f(1,2,3);`, + nil, ARR{1, 2, 3}) + + expectRun(t, `f := func(a, b, ...x) { return [a, b, x]; }; out = f(8,9,1,2,3);`, + nil, ARR{8, 9, ARR{1, 2, 3}}) + + expectRun(t, `f := func(v) { x := 2; return func(a, ...b){ return [a, b, v+x]}; }; out = f(5)("a", "b");`, + nil, ARR{"a", ARR{"b"}, 7}) + + expectRun(t, `f := func(...x) { return x; }; out = f();`, + nil, &tengo.Array{Value: []tengo.Object{}}) + + expectRun(t, `f := func(a, b, ...x) { return [a, b, x]; }; out = f(8, 9);`, + nil, ARR{8, 9, ARR{}}) + + expectRun(t, `f := func(v) { x := 2; return func(a, ...b){ return [a, b, v+x]}; }; out = f(5)("a");`, + nil, ARR{"a", ARR{}, 7}) + + expectError(t, `f := func(a, b, ...x) { return [a, b, x]; }; f();`, nil, + "Runtime Error: wrong number of arguments: want>=2, got=0\n\tat test:1:46") + + expectError(t, `f := func(a, b, ...x) { return [a, b, x]; }; f(1);`, nil, + "Runtime Error: wrong number of arguments: want>=2, got=1\n\tat test:1:46") + + expectRun(t, `f := func(x) { return x; }; out = f(5);`, nil, 5) + expectRun(t, `f := func(x) { return x * 2; }; out = f(5);`, nil, 10) + expectRun(t, `f := func(x, y) { return x + y; }; out = f(5, 5);`, nil, 10) + expectRun(t, `f := func(x, y) { return x + y; }; out = f(5 + 5, f(5, 5));`, + nil, 20) + expectRun(t, `out = func(x) { return x; }(5)`, nil, 5) + expectRun(t, `x := 10; f := func(x) { return x; }; f(5); out = x;`, nil, 10) + + expectRun(t, ` + f2 := func(a) { + f1 := func(a) { + return a * 2; + }; + + return f1(a) * 3; + }; + + out = f2(10); + `, nil, 60) + + expectRun(t, ` + f1 := func(f) { + a := [undefined] + a[0] = func() { return f(a) } + return a[0]() + } + + out = f1(func(a) { return 2 }) + `, nil, 2) + + // closures + expectRun(t, ` + newAdder := func(x) { + return func(y) { return x + y }; + }; + + add2 := newAdder(2); + out = add2(5); + `, nil, 7) + + // function as a argument + expectRun(t, ` + add := func(a, b) { return a + b }; + sub := func(a, b) { return a - b }; + applyFunc := func(a, b, f) { return f(a, b) }; + + out = applyFunc(applyFunc(2, 2, add), 3, sub); + `, nil, 1) + + expectRun(t, `f1 := func() { return 5 + 10; }; out = f1();`, + nil, 15) + expectRun(t, `f1 := func() { return 1 }; f2 := func() { return 2 }; out = f1() + f2()`, + nil, 3) + expectRun(t, `f1 := func() { return 1 }; f2 := func() { return f1() + 2 }; f3 := func() { return f2() + 3 }; out = f3()`, + nil, 6) + expectRun(t, `f1 := func() { return 99; 100 }; out = f1();`, + nil, 99) + expectRun(t, `f1 := func() { return 99; return 100 }; out = f1();`, + nil, 99) + expectRun(t, `f1 := func() { return 33; }; f2 := func() { return f1 }; out = f2()();`, + nil, 33) + expectRun(t, `one := func() { one = 1; return one }; out = one()`, + nil, 1) + expectRun(t, `three := func() { one := 1; two := 2; return one + two }; out = three()`, + nil, 3) + expectRun(t, `three := func() { one := 1; two := 2; return one + two }; seven := func() { three := 3; four := 4; return three + four }; out = three() + seven()`, + nil, 10) + expectRun(t, ` + foo1 := func() { + foo := 50 + return foo + } + foo2 := func() { + foo := 100 + return foo + } + out = foo1() + foo2()`, nil, 150) + expectRun(t, ` + g := 50; + minusOne := func() { + n := 1; + return g - n; + }; + minusTwo := func() { + n := 2; + return g - n; + }; + out = minusOne() + minusTwo() + `, nil, 97) + expectRun(t, ` + f1 := func() { + f2 := func() { return 1; } + return f2 + }; + out = f1()() + `, nil, 1) + + expectRun(t, ` + f1 := func(a) { return a; }; + out = f1(4)`, nil, 4) + expectRun(t, ` + f1 := func(a, b) { return a + b; }; + out = f1(1, 2)`, nil, 3) + + expectRun(t, ` + sum := func(a, b) { + c := a + b; + return c; + }; + out = sum(1, 2);`, nil, 3) + + expectRun(t, ` + sum := func(a, b) { + c := a + b; + return c; + }; + out = sum(1, 2) + sum(3, 4);`, nil, 10) + + expectRun(t, ` + sum := func(a, b) { + c := a + b + return c + }; + outer := func() { + return sum(1, 2) + sum(3, 4) + }; + out = outer();`, nil, 10) + + expectRun(t, ` + g := 10; + + sum := func(a, b) { + c := a + b; + return c + g; + } + + outer := func() { + return sum(1, 2) + sum(3, 4) + g; + } + + out = outer() + g + `, nil, 50) + + expectError(t, `func() { return 1; }(1)`, + nil, "wrong number of arguments") + expectError(t, `func(a) { return a; }()`, + nil, "wrong number of arguments") + expectError(t, `func(a, b) { return a + b; }(1)`, + nil, "wrong number of arguments") + + expectRun(t, ` + f1 := func(a) { + return func() { return a; }; + }; + f2 := f1(99); + out = f2() + `, nil, 99) + + expectRun(t, ` + f1 := func(a, b) { + return func(c) { return a + b + c }; + }; + + f2 := f1(1, 2); + out = f2(8); + `, nil, 11) + expectRun(t, ` + f1 := func(a, b) { + c := a + b; + return func(d) { return c + d }; + }; + f2 := f1(1, 2); + out = f2(8); + `, nil, 11) + expectRun(t, ` + f1 := func(a, b) { + c := a + b; + return func(d) { + e := d + c; + return func(f) { return e + f }; + } + }; + f2 := f1(1, 2); + f3 := f2(3); + out = f3(8); + `, nil, 14) + expectRun(t, ` + a := 1; + f1 := func(b) { + return func(c) { + return func(d) { return a + b + c + d } + }; + }; + f2 := f1(2); + f3 := f2(3); + out = f3(8); + `, nil, 14) + expectRun(t, ` + f1 := func(a, b) { + one := func() { return a; }; + two := func() { return b; }; + return func() { return one() + two(); } + }; + f2 := f1(9, 90); + out = f2(); + `, nil, 99) + + // global function recursion + expectRun(t, ` + fib := func(x) { + if x == 0 { + return 0 + } else if x == 1 { + return 1 + } else { + return fib(x-1) + fib(x-2) + } + } + out = fib(15)`, nil, 610) + + // local function recursion + expectRun(t, ` +out = func() { + sum := func(x) { + return x == 0 ? 0 : x + sum(x-1) + } + return sum(5) +}()`, nil, 15) + + expectError(t, `return 5`, nil, "return not allowed outside function") + + // closure and block scopes + expectRun(t, ` +func() { + a := 10 + func() { + b := 5 + if true { + out = a + 5 + } + }() +}()`, nil, 15) + expectRun(t, ` +func() { + a := 10 + b := func() { return 5 } + func() { + if b() { + out = a + b() + } + }() +}()`, nil, 15) + expectRun(t, ` +func() { + a := 10 + func() { + b := func() { return 5 } + func() { + if true { + out = a + b() + } + }() + }() +}()`, nil, 15) + + // function skipping return + expectRun(t, `out = func() {}()`, + nil, tengo.UndefinedValue) + expectRun(t, `out = func(v) { if v { return true } }(1)`, + nil, true) + expectRun(t, `out = func(v) { if v { return true } }(0)`, + nil, tengo.UndefinedValue) + expectRun(t, `out = func(v) { if v { } else { return true } }(1)`, + nil, tengo.UndefinedValue) + expectRun(t, `out = func(v) { if v { return } }(1)`, + nil, tengo.UndefinedValue) + expectRun(t, `out = func(v) { if v { return } }(0)`, + nil, tengo.UndefinedValue) + expectRun(t, `out = func(v) { if v { } else { return } }(1)`, + nil, tengo.UndefinedValue) + expectRun(t, `out = func(v) { for ;;v++ { if v == 3 { return true } } }(1)`, + nil, true) + expectRun(t, `out = func(v) { for ;;v++ { if v == 3 { break } } }(1)`, + nil, tengo.UndefinedValue) +} + +func TestIf(t *testing.T) { + expectRun(t, `if (true) { out = 10 }`, nil, 10) + expectRun(t, `if (false) { out = 10 }`, nil, tengo.UndefinedValue) + expectRun(t, `if (false) { out = 10 } else { out = 20 }`, nil, 20) + expectRun(t, `if (1) { out = 10 }`, nil, 10) + expectRun(t, `if (0) { out = 10 } else { out = 20 }`, nil, 20) + expectRun(t, `if (1 < 2) { out = 10 }`, nil, 10) + expectRun(t, `if (1 > 2) { out = 10 }`, nil, tengo.UndefinedValue) + expectRun(t, `if (1 < 2) { out = 10 } else { out = 20 }`, nil, 10) + expectRun(t, `if (1 > 2) { out = 10 } else { out = 20 }`, nil, 20) + + expectRun(t, `if (1 < 2) { out = 10 } else if (1 > 2) { out = 20 } else { out = 30 }`, + nil, 10) + expectRun(t, `if (1 > 2) { out = 10 } else if (1 < 2) { out = 20 } else { out = 30 }`, + nil, 20) + expectRun(t, `if (1 > 2) { out = 10 } else if (1 == 2) { out = 20 } else { out = 30 }`, + nil, 30) + expectRun(t, `if (1 > 2) { out = 10 } else if (1 == 2) { out = 20 } else if (1 < 2) { out = 30 } else { out = 40 }`, + nil, 30) + expectRun(t, `if (1 > 2) { out = 10 } else if (1 < 2) { out = 20; out = 21; out = 22 } else { out = 30 }`, + nil, 22) + expectRun(t, `if (1 > 2) { out = 10 } else if (1 == 2) { out = 20 } else { out = 30; out = 31; out = 32}`, + nil, 32) + expectRun(t, `if (1 > 2) { out = 10 } else if (1 < 2) { if (1 == 2) { out = 21 } else { out = 22 } } else { out = 30 }`, + nil, 22) + expectRun(t, `if (1 > 2) { out = 10 } else if (1 < 2) { if (1 == 2) { out = 21 } else if (2 == 3) { out = 22 } else { out = 23 } } else { out = 30 }`, + nil, 23) + expectRun(t, `if (1 > 2) { out = 10 } else if (1 == 2) { if (1 == 2) { out = 21 } else if (2 == 3) { out = 22 } else { out = 23 } } else { out = 30 }`, + nil, 30) + expectRun(t, `if (1 > 2) { out = 10 } else if (1 == 2) { out = 20 } else { if (1 == 2) { out = 31 } else if (2 == 3) { out = 32 } else { out = 33 } }`, + nil, 33) + + expectRun(t, `if a:=0; a<1 { out = 10 }`, nil, 10) + expectRun(t, `a:=0; if a++; a==1 { out = 10 }`, nil, 10) + expectRun(t, ` +func() { + a := 1 + if a++; a > 1 { + out = a + } +}() +`, nil, 2) + expectRun(t, ` +func() { + a := 1 + if a++; a == 1 { + out = 10 + } else { + out = 20 + } +}() +`, nil, 20) + expectRun(t, ` +func() { + a := 1 + + func() { + if a++; a > 1 { + a++ + } + }() + + out = a +}() +`, nil, 3) + + // expression statement in init (should not leave objects on stack) + expectRun(t, `a := 1; if a; a { out = a }`, nil, 1) + expectRun(t, `a := 1; if a + 4; a { out = a }`, nil, 1) + + // dead code elimination + expectRun(t, ` +out = func() { + if false { return 1 } + + a := undefined + + a = 2 + if !a { + b := func() { + return is_callable(a) ? a(8) : a + }() + if is_error(b) { + return b + } else if !is_undefined(b) { + return immutable(b) + } + } + + a = 3 + if a { + b := func() { + return is_callable(a) ? a(9) : a + }() + if is_error(b) { + return b + } else if !is_undefined(b) { + return immutable(b) + } + } + + return a +}() +`, nil, 3) +} + +func TestImmutable(t *testing.T) { + // primitive types are already immutable values + // immutable expression has no effects. + expectRun(t, `a := immutable(1); out = a`, nil, 1) + expectRun(t, `a := 5; b := immutable(a); out = b`, nil, 5) + expectRun(t, `a := immutable(1); a = 5; out = a`, nil, 5) + + // array + expectError(t, `a := immutable([1, 2, 3]); a[1] = 5`, + nil, "not index-assignable") + expectError(t, `a := immutable(["foo", [1,2,3]]); a[1] = "bar"`, + nil, "not index-assignable") + expectRun(t, `a := immutable(["foo", [1,2,3]]); a[1][1] = "bar"; out = a`, + nil, IARR{"foo", ARR{1, "bar", 3}}) + expectError(t, `a := immutable(["foo", immutable([1,2,3])]); a[1][1] = "bar"`, + nil, "not index-assignable") + expectError(t, `a := ["foo", immutable([1,2,3])]; a[1][1] = "bar"`, + nil, "not index-assignable") + expectRun(t, `a := immutable([1,2,3]); b := copy(a); b[1] = 5; out = b`, + nil, ARR{1, 5, 3}) + expectRun(t, `a := immutable([1,2,3]); b := copy(a); b[1] = 5; out = a`, + nil, IARR{1, 2, 3}) + expectRun(t, `out = immutable([1,2,3]) == [1,2,3]`, + nil, true) + expectRun(t, `out = immutable([1,2,3]) == immutable([1,2,3])`, + nil, true) + expectRun(t, `out = [1,2,3] == immutable([1,2,3])`, + nil, true) + expectRun(t, `out = immutable([1,2,3]) == [1,2]`, + nil, false) + expectRun(t, `out = immutable([1,2,3]) == immutable([1,2])`, + nil, false) + expectRun(t, `out = [1,2,3] == immutable([1,2])`, + nil, false) + expectRun(t, `out = immutable([1, 2, 3, 4])[1]`, + nil, 2) + expectRun(t, `out = immutable([1, 2, 3, 4])[1:3]`, + nil, ARR{2, 3}) + expectRun(t, `a := immutable([1,2,3]); a = 5; out = a`, + nil, 5) + expectRun(t, `a := immutable([1, 2, 3]); out = a[5]`, + nil, tengo.UndefinedValue) + + // map + expectError(t, `a := immutable({b: 1, c: 2}); a.b = 5`, + nil, "not index-assignable") + expectError(t, `a := immutable({b: 1, c: 2}); a["b"] = "bar"`, + nil, "not index-assignable") + expectRun(t, `a := immutable({b: 1, c: [1,2,3]}); a.c[1] = "bar"; out = a`, + nil, IMAP{"b": 1, "c": ARR{1, "bar", 3}}) + expectError(t, `a := immutable({b: 1, c: immutable([1,2,3])}); a.c[1] = "bar"`, + nil, "not index-assignable") + expectError(t, `a := {b: 1, c: immutable([1,2,3])}; a.c[1] = "bar"`, + nil, "not index-assignable") + expectRun(t, `out = immutable({a:1,b:2}) == {a:1,b:2}`, + nil, true) + expectRun(t, `out = immutable({a:1,b:2}) == immutable({a:1,b:2})`, + nil, true) + expectRun(t, `out = {a:1,b:2} == immutable({a:1,b:2})`, + nil, true) + expectRun(t, `out = immutable({a:1,b:2}) == {a:1,b:3}`, + nil, false) + expectRun(t, `out = immutable({a:1,b:2}) == immutable({a:1,b:3})`, + nil, false) + expectRun(t, `out = {a:1,b:2} == immutable({a:1,b:3})`, + nil, false) + expectRun(t, `out = immutable({a:1,b:2}).b`, + nil, 2) + expectRun(t, `out = immutable({a:1,b:2})["b"]`, + nil, 2) + expectRun(t, `a := immutable({a:1,b:2}); a = 5; out = 5`, + nil, 5) + expectRun(t, `a := immutable({a:1,b:2}); out = a.c`, + nil, tengo.UndefinedValue) + + expectRun(t, `a := immutable({b: 5, c: "foo"}); out = a.b`, + nil, 5) + expectError(t, `a := immutable({b: 5, c: "foo"}); a.b = 10`, + nil, "not index-assignable") +} + +func TestIncDec(t *testing.T) { + expectRun(t, `out = 0; out++`, nil, 1) + expectRun(t, `out = 0; out--`, nil, -1) + expectRun(t, `a := 0; a++; out = a`, nil, 1) + expectRun(t, `a := 0; a++; a--; out = a`, nil, 0) + + // this seems strange but it works because 'a += b' is + // translated into 'a = a + b' and string type takes other types for + operator. + expectRun(t, `a := "foo"; a++; out = a`, nil, "foo1") + expectError(t, `a := "foo"; a--`, nil, "invalid operation") + + expectError(t, `a++`, nil, "unresolved reference") // not declared + expectError(t, `a--`, nil, "unresolved reference") // not declared + expectError(t, `4++`, nil, "unresolved reference") +} + +type StringDict struct { + tengo.ObjectImpl + Value map[string]string +} + +func (o *StringDict) String() string { return "" } + +func (o *StringDict) TypeName() string { + return "string-dict" +} + +func (o *StringDict) IndexGet(index tengo.Object) (tengo.Object, error) { + strIdx, ok := index.(*tengo.String) + if !ok { + return nil, tengo.ErrInvalidIndexType + } + + for k, v := range o.Value { + if strings.EqualFold(strIdx.Value, k) { + return &tengo.String{Value: v}, nil + } + } + + return tengo.UndefinedValue, nil +} + +func (o *StringDict) IndexSet(index, value tengo.Object) error { + strIdx, ok := index.(*tengo.String) + if !ok { + return tengo.ErrInvalidIndexType + } + + strVal, ok := tengo.ToString(value) + if !ok { + return tengo.ErrInvalidIndexValueType + } + + o.Value[strings.ToLower(strIdx.Value)] = strVal + + return nil +} + +type StringCircle struct { + tengo.ObjectImpl + Value []string +} + +func (o *StringCircle) TypeName() string { + return "string-circle" +} + +func (o *StringCircle) String() string { + return "" +} + +func (o *StringCircle) IndexGet(index tengo.Object) (tengo.Object, error) { + intIdx, ok := index.(*tengo.Int) + if !ok { + return nil, tengo.ErrInvalidIndexType + } + + r := int(intIdx.Value) % len(o.Value) + if r < 0 { + r = len(o.Value) + r + } + + return &tengo.String{Value: o.Value[r]}, nil +} + +func (o *StringCircle) IndexSet(index, value tengo.Object) error { + intIdx, ok := index.(*tengo.Int) + if !ok { + return tengo.ErrInvalidIndexType + } + + r := int(intIdx.Value) % len(o.Value) + if r < 0 { + r = len(o.Value) + r + } + + strVal, ok := tengo.ToString(value) + if !ok { + return tengo.ErrInvalidIndexValueType + } + + o.Value[r] = strVal + + return nil +} + +type StringArray struct { + tengo.ObjectImpl + Value []string +} + +func (o *StringArray) String() string { + return strings.Join(o.Value, ", ") +} + +func (o *StringArray) BinaryOp( + op token.Token, + rhs tengo.Object, +) (tengo.Object, error) { + if rhs, ok := rhs.(*StringArray); ok { + switch op { + case token.Add: + if len(rhs.Value) == 0 { + return o, nil + } + return &StringArray{Value: append(o.Value, rhs.Value...)}, nil + } + } + + return nil, tengo.ErrInvalidOperator +} + +func (o *StringArray) IsFalsy() bool { + return len(o.Value) == 0 +} + +func (o *StringArray) Equals(x tengo.Object) bool { + if x, ok := x.(*StringArray); ok { + if len(o.Value) != len(x.Value) { + return false + } + + for i, v := range o.Value { + if v != x.Value[i] { + return false + } + } + + return true + } + + return false +} + +func (o *StringArray) Copy() tengo.Object { + return &StringArray{ + Value: append([]string{}, o.Value...), + } +} + +func (o *StringArray) TypeName() string { + return "string-array" +} + +func (o *StringArray) IndexGet(index tengo.Object) (tengo.Object, error) { + intIdx, ok := index.(*tengo.Int) + if ok { + if intIdx.Value >= 0 && intIdx.Value < int64(len(o.Value)) { + return &tengo.String{Value: o.Value[intIdx.Value]}, nil + } + + return nil, tengo.ErrIndexOutOfBounds + } + + strIdx, ok := index.(*tengo.String) + if ok { + for vidx, str := range o.Value { + if strIdx.Value == str { + return &tengo.Int{Value: int64(vidx)}, nil + } + } + + return tengo.UndefinedValue, nil + } + + return nil, tengo.ErrInvalidIndexType +} + +func (o *StringArray) IndexSet(index, value tengo.Object) error { + strVal, ok := tengo.ToString(value) + if !ok { + return tengo.ErrInvalidIndexValueType + } + + intIdx, ok := index.(*tengo.Int) + if ok { + if intIdx.Value >= 0 && intIdx.Value < int64(len(o.Value)) { + o.Value[intIdx.Value] = strVal + return nil + } + + return tengo.ErrIndexOutOfBounds + } + + return tengo.ErrInvalidIndexType +} + +func (o *StringArray) Call( + args ...tengo.Object, +) (ret tengo.Object, err error) { + if len(args) != 1 { + return nil, tengo.ErrWrongNumArguments + } + + s1, ok := tengo.ToString(args[0]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + for i, v := range o.Value { + if v == s1 { + return &tengo.Int{Value: int64(i)}, nil + } + } + + return tengo.UndefinedValue, nil +} + +func (o *StringArray) CanCall() bool { + return true +} + +func TestIndexable(t *testing.T) { + dict := func() *StringDict { + return &StringDict{Value: map[string]string{"a": "foo", "b": "bar"}} + } + expectRun(t, `out = dict["a"]`, + Opts().Symbol("dict", dict()).Skip2ndPass(), "foo") + expectRun(t, `out = dict["B"]`, + Opts().Symbol("dict", dict()).Skip2ndPass(), "bar") + expectRun(t, `out = dict["x"]`, + Opts().Symbol("dict", dict()).Skip2ndPass(), tengo.UndefinedValue) + expectError(t, `dict[0]`, + Opts().Symbol("dict", dict()).Skip2ndPass(), "invalid index type") + + strCir := func() *StringCircle { + return &StringCircle{Value: []string{"one", "two", "three"}} + } + expectRun(t, `out = cir[0]`, + Opts().Symbol("cir", strCir()).Skip2ndPass(), "one") + expectRun(t, `out = cir[1]`, + Opts().Symbol("cir", strCir()).Skip2ndPass(), "two") + expectRun(t, `out = cir[-1]`, + Opts().Symbol("cir", strCir()).Skip2ndPass(), "three") + expectRun(t, `out = cir[-2]`, + Opts().Symbol("cir", strCir()).Skip2ndPass(), "two") + expectRun(t, `out = cir[3]`, + Opts().Symbol("cir", strCir()).Skip2ndPass(), "one") + expectError(t, `cir["a"]`, + Opts().Symbol("cir", strCir()).Skip2ndPass(), "invalid index type") + + strArr := func() *StringArray { + return &StringArray{Value: []string{"one", "two", "three"}} + } + expectRun(t, `out = arr["one"]`, + Opts().Symbol("arr", strArr()).Skip2ndPass(), 0) + expectRun(t, `out = arr["three"]`, + Opts().Symbol("arr", strArr()).Skip2ndPass(), 2) + expectRun(t, `out = arr["four"]`, + Opts().Symbol("arr", strArr()).Skip2ndPass(), tengo.UndefinedValue) + expectRun(t, `out = arr[0]`, + Opts().Symbol("arr", strArr()).Skip2ndPass(), "one") + expectRun(t, `out = arr[1]`, + Opts().Symbol("arr", strArr()).Skip2ndPass(), "two") + expectError(t, `arr[-1]`, + Opts().Symbol("arr", strArr()).Skip2ndPass(), "index out of bounds") +} + +func TestIndexAssignable(t *testing.T) { + dict := func() *StringDict { + return &StringDict{Value: map[string]string{"a": "foo", "b": "bar"}} + } + expectRun(t, `dict["a"] = "1984"; out = dict["a"]`, + Opts().Symbol("dict", dict()).Skip2ndPass(), "1984") + expectRun(t, `dict["c"] = "1984"; out = dict["c"]`, + Opts().Symbol("dict", dict()).Skip2ndPass(), "1984") + expectRun(t, `dict["c"] = 1984; out = dict["C"]`, + Opts().Symbol("dict", dict()).Skip2ndPass(), "1984") + expectError(t, `dict[0] = "1984"`, + Opts().Symbol("dict", dict()).Skip2ndPass(), "invalid index type") + + strCir := func() *StringCircle { + return &StringCircle{Value: []string{"one", "two", "three"}} + } + expectRun(t, `cir[0] = "ONE"; out = cir[0]`, + Opts().Symbol("cir", strCir()).Skip2ndPass(), "ONE") + expectRun(t, `cir[1] = "TWO"; out = cir[1]`, + Opts().Symbol("cir", strCir()).Skip2ndPass(), "TWO") + expectRun(t, `cir[-1] = "THREE"; out = cir[2]`, + Opts().Symbol("cir", strCir()).Skip2ndPass(), "THREE") + expectRun(t, `cir[0] = "ONE"; out = cir[3]`, + Opts().Symbol("cir", strCir()).Skip2ndPass(), "ONE") + expectError(t, `cir["a"] = "ONE"`, + Opts().Symbol("cir", strCir()).Skip2ndPass(), "invalid index type") + + strArr := func() *StringArray { + return &StringArray{Value: []string{"one", "two", "three"}} + } + expectRun(t, `arr[0] = "ONE"; out = arr[0]`, + Opts().Symbol("arr", strArr()).Skip2ndPass(), "ONE") + expectRun(t, `arr[1] = "TWO"; out = arr[1]`, + Opts().Symbol("arr", strArr()).Skip2ndPass(), "TWO") + expectError(t, `arr["one"] = "ONE"`, + Opts().Symbol("arr", strArr()).Skip2ndPass(), "invalid index type") +} + +func TestInteger(t *testing.T) { + expectRun(t, `out = 5`, nil, 5) + expectRun(t, `out = 10`, nil, 10) + expectRun(t, `out = -5`, nil, -5) + expectRun(t, `out = -10`, nil, -10) + expectRun(t, `out = 5 + 5 + 5 + 5 - 10`, nil, 10) + expectRun(t, `out = 2 * 2 * 2 * 2 * 2`, nil, 32) + expectRun(t, `out = -50 + 100 + -50`, nil, 0) + expectRun(t, `out = 5 * 2 + 10`, nil, 20) + expectRun(t, `out = 5 + 2 * 10`, nil, 25) + expectRun(t, `out = 20 + 2 * -10`, nil, 0) + expectRun(t, `out = 50 / 2 * 2 + 10`, nil, 60) + expectRun(t, `out = 2 * (5 + 10)`, nil, 30) + expectRun(t, `out = 3 * 3 * 3 + 10`, nil, 37) + expectRun(t, `out = 3 * (3 * 3) + 10`, nil, 37) + expectRun(t, `out = (5 + 10 * 2 + 15 /3) * 2 + -10`, nil, 50) + expectRun(t, `out = 5 % 3`, nil, 2) + expectRun(t, `out = 5 % 3 + 4`, nil, 6) + expectRun(t, `out = +5`, nil, 5) + expectRun(t, `out = +5 + -5`, nil, 0) + + expectRun(t, `out = 9 + '0'`, nil, '9') + expectRun(t, `out = '9' - 5`, nil, '4') +} + +type StringArrayIterator struct { + tengo.ObjectImpl + strArr *StringArray + idx int +} + +func (i *StringArrayIterator) TypeName() string { + return "string-array-iterator" +} + +func (i *StringArrayIterator) String() string { + return "" +} + +func (i *StringArrayIterator) Next() bool { + i.idx++ + return i.idx <= len(i.strArr.Value) +} + +func (i *StringArrayIterator) Key() tengo.Object { + return &tengo.Int{Value: int64(i.idx - 1)} +} + +func (i *StringArrayIterator) Value() tengo.Object { + return &tengo.String{Value: i.strArr.Value[i.idx-1]} +} + +func (o *StringArray) Iterate() tengo.Iterator { + return &StringArrayIterator{ + strArr: o, + } +} + +func (o *StringArray) CanIterate() bool { + return true +} + +func TestIterable(t *testing.T) { + strArr := func() *StringArray { + return &StringArray{Value: []string{"one", "two", "three"}} + } + expectRun(t, `for i, s in arr { out += i }`, + Opts().Symbol("arr", strArr()).Skip2ndPass(), 3) + expectRun(t, `for i, s in arr { out += s }`, + Opts().Symbol("arr", strArr()).Skip2ndPass(), "onetwothree") + expectRun(t, `for i, s in arr { out += s + i }`, + Opts().Symbol("arr", strArr()).Skip2ndPass(), "one0two1three2") +} + +func TestLogical(t *testing.T) { + expectRun(t, `out = true && true`, nil, true) + expectRun(t, `out = true && false`, nil, false) + expectRun(t, `out = false && true`, nil, false) + expectRun(t, `out = false && false`, nil, false) + expectRun(t, `out = !true && true`, nil, false) + expectRun(t, `out = !true && false`, nil, false) + expectRun(t, `out = !false && true`, nil, true) + expectRun(t, `out = !false && false`, nil, false) + + expectRun(t, `out = true || true`, nil, true) + expectRun(t, `out = true || false`, nil, true) + expectRun(t, `out = false || true`, nil, true) + expectRun(t, `out = false || false`, nil, false) + expectRun(t, `out = !true || true`, nil, true) + expectRun(t, `out = !true || false`, nil, false) + expectRun(t, `out = !false || true`, nil, true) + expectRun(t, `out = !false || false`, nil, true) + + expectRun(t, `out = 1 && 2`, nil, 2) + expectRun(t, `out = 1 || 2`, nil, 1) + expectRun(t, `out = 1 && 0`, nil, 0) + expectRun(t, `out = 1 || 0`, nil, 1) + expectRun(t, `out = 1 && (0 || 2)`, nil, 2) + expectRun(t, `out = 0 || (0 || 2)`, nil, 2) + expectRun(t, `out = 0 || (0 && 2)`, nil, 0) + expectRun(t, `out = 0 || (2 && 0)`, nil, 0) + + expectRun(t, `t:=func() {out = 3; return true}; f:=func() {out = 7; return false}; t() && f()`, + nil, 7) + expectRun(t, `t:=func() {out = 3; return true}; f:=func() {out = 7; return false}; f() && t()`, + nil, 7) + expectRun(t, `t:=func() {out = 3; return true}; f:=func() {out = 7; return false}; f() || t()`, + nil, 3) + expectRun(t, `t:=func() {out = 3; return true}; f:=func() {out = 7; return false}; t() || f()`, + nil, 3) + expectRun(t, `t:=func() {out = 3; return true}; f:=func() {out = 7; return false}; !t() && f()`, + nil, 3) + expectRun(t, `t:=func() {out = 3; return true}; f:=func() {out = 7; return false}; !f() && t()`, + nil, 3) + expectRun(t, `t:=func() {out = 3; return true}; f:=func() {out = 7; return false}; !f() || t()`, + nil, 7) + expectRun(t, `t:=func() {out = 3; return true}; f:=func() {out = 7; return false}; !t() || f()`, + nil, 7) +} + +func TestMap(t *testing.T) { + expectRun(t, ` +out = { + one: 10 - 9, + two: 1 + 1, + three: 6 / 2 +}`, nil, MAP{ + "one": 1, + "two": 2, + "three": 3, + }) + + expectRun(t, ` +out = { + "one": 10 - 9, + "two": 1 + 1, + "three": 6 / 2 +}`, nil, MAP{ + "one": 1, + "two": 2, + "three": 3, + }) + + expectRun(t, `out = {foo: 5}["foo"]`, nil, 5) + expectRun(t, `out = {foo: 5}["bar"]`, nil, tengo.UndefinedValue) + expectRun(t, `key := "foo"; out = {foo: 5}[key]`, nil, 5) + expectRun(t, `out = {}["foo"]`, nil, tengo.UndefinedValue) + + expectRun(t, ` +m := { + foo: func(x) { + return x * 2 + } +} +out = m["foo"](2) + m["foo"](3) +`, nil, 10) + + // map assignment is copy-by-reference + expectRun(t, `m1 := {k1: 1, k2: "foo"}; m2 := m1; m1.k1 = 5; out = m2.k1`, + nil, 5) + expectRun(t, `m1 := {k1: 1, k2: "foo"}; m2 := m1; m2.k1 = 3; out = m1.k1`, + nil, 3) + expectRun(t, `func() { m1 := {k1: 1, k2: "foo"}; m2 := m1; m1.k1 = 5; out = m2.k1 }()`, + nil, 5) + expectRun(t, `func() { m1 := {k1: 1, k2: "foo"}; m2 := m1; m2.k1 = 3; out = m1.k1 }()`, + nil, 3) +} + +func TestBuiltin(t *testing.T) { + m := Opts().Module("math", + &tengo.BuiltinModule{ + Attrs: map[string]tengo.Object{ + "abs": &tengo.UserFunction{ + Name: "abs", + Value: func(a ...tengo.Object) (tengo.Object, error) { + v, _ := tengo.ToFloat64(a[0]) + return &tengo.Float{Value: math.Abs(v)}, nil + }, + }, + }, + }) + + // builtin + expectRun(t, `math := import("math"); out = math.abs(1)`, m, 1.0) + expectRun(t, `math := import("math"); out = math.abs(-1)`, m, 1.0) + expectRun(t, `math := import("math"); out = math.abs(1.0)`, m, 1.0) + expectRun(t, `math := import("math"); out = math.abs(-1.0)`, m, 1.0) +} + +func TestUserModules(t *testing.T) { + // export none + expectRun(t, `out = import("mod1")`, + Opts().Module("mod1", `fn := func() { return 5.0 }; a := 2`), + tengo.UndefinedValue) + + // export values + expectRun(t, `out = import("mod1")`, + Opts().Module("mod1", `export 5`), 5) + expectRun(t, `out = import("mod1")`, + Opts().Module("mod1", `export "foo"`), "foo") + + // export compound types + expectRun(t, `out = import("mod1")`, + Opts().Module("mod1", `export [1, 2, 3]`), IARR{1, 2, 3}) + expectRun(t, `out = import("mod1")`, + Opts().Module("mod1", `export {a: 1, b: 2}`), IMAP{"a": 1, "b": 2}) + + // export value is immutable + expectError(t, `m1 := import("mod1"); m1.a = 5`, + Opts().Module("mod1", `export {a: 1, b: 2}`), "not index-assignable") + expectError(t, `m1 := import("mod1"); m1[1] = 5`, + Opts().Module("mod1", `export [1, 2, 3]`), "not index-assignable") + + // code after export statement will not be executed + expectRun(t, `out = import("mod1")`, + Opts().Module("mod1", `a := 10; export a; a = 20`), 10) + expectRun(t, `out = import("mod1")`, + Opts().Module("mod1", `a := 10; export a; a = 20; export a`), 10) + + // export function + expectRun(t, `out = import("mod1")()`, + Opts().Module("mod1", `export func() { return 5.0 }`), 5.0) + // export function that reads module-global variable + expectRun(t, `out = import("mod1")()`, + Opts().Module("mod1", `a := 1.5; export func() { return a + 5.0 }`), 6.5) + // export function that read local variable + expectRun(t, `out = import("mod1")()`, + Opts().Module("mod1", `export func() { a := 1.5; return a + 5.0 }`), 6.5) + // export function that read free variables + expectRun(t, `out = import("mod1")()`, + Opts().Module("mod1", `export func() { a := 1.5; return func() { return a + 5.0 }() }`), 6.5) + + // recursive function in module + expectRun(t, `out = import("mod1")`, + Opts().Module( + "mod1", ` +a := func(x) { + return x == 0 ? 0 : x + a(x-1) +} + +export a(5) +`), 15) + expectRun(t, `out = import("mod1")`, + Opts().Module( + "mod1", ` +export func() { + a := func(x) { + return x == 0 ? 0 : x + a(x-1) + } + + return a(5) +}() +`), 15) + + // (main) -> mod1 -> mod2 + expectRun(t, `out = import("mod1")()`, + Opts().Module("mod1", `export import("mod2")`). + Module("mod2", `export func() { return 5.0 }`), + 5.0) + // (main) -> mod1 -> mod2 + // -> mod2 + expectRun(t, `import("mod1"); out = import("mod2")()`, + Opts().Module("mod1", `export import("mod2")`). + Module("mod2", `export func() { return 5.0 }`), + 5.0) + // (main) -> mod1 -> mod2 -> mod3 + // -> mod2 -> mod3 + expectRun(t, `import("mod1"); out = import("mod2")()`, + Opts().Module("mod1", `export import("mod2")`). + Module("mod2", `export import("mod3")`). + Module("mod3", `export func() { return 5.0 }`), + 5.0) + + // cyclic imports + // (main) -> mod1 -> mod2 -> mod1 + expectError(t, `import("mod1")`, + Opts().Module("mod1", `import("mod2")`). + Module("mod2", `import("mod1")`), + "Compile Error: cyclic module import: mod1\n\tat mod2:1:1") + // (main) -> mod1 -> mod2 -> mod3 -> mod1 + expectError(t, `import("mod1")`, + Opts().Module("mod1", `import("mod2")`). + Module("mod2", `import("mod3")`). + Module("mod3", `import("mod1")`), + "Compile Error: cyclic module import: mod1\n\tat mod3:1:1") + // (main) -> mod1 -> mod2 -> mod3 -> mod2 + expectError(t, `import("mod1")`, + Opts().Module("mod1", `import("mod2")`). + Module("mod2", `import("mod3")`). + Module("mod3", `import("mod2")`), + "Compile Error: cyclic module import: mod2\n\tat mod3:1:1") + + // unknown modules + expectError(t, `import("mod0")`, + Opts().Module("mod1", `a := 5`), "module 'mod0' not found") + expectError(t, `import("mod1")`, + Opts().Module("mod1", `import("mod2")`), "module 'mod2' not found") + + // module is immutable but its variables is not necessarily immutable. + expectRun(t, `m1 := import("mod1"); m1.a.b = 5; out = m1.a.b`, + Opts().Module("mod1", `export {a: {b: 3}}`), + 5) + + // make sure module has same builtin functions + expectRun(t, `out = import("mod1")`, + Opts().Module("mod1", `export func() { return type_name(0) }()`), + "int") + + // 'export' statement is ignored outside module + expectRun(t, `a := 5; export func() { a = 10 }(); out = a`, + Opts().Skip2ndPass(), 5) + + // 'export' must be in the top-level + expectError(t, `import("mod1")`, + Opts().Module("mod1", `func() { export 5 }()`), + "Compile Error: export not allowed inside function\n\tat mod1:1:10") + expectError(t, `import("mod1")`, + Opts().Module("mod1", `func() { func() { export 5 }() }()`), + "Compile Error: export not allowed inside function\n\tat mod1:1:19") + + // module cannot access outer scope + expectError(t, `a := 5; import("mod1")`, + Opts().Module("mod1", `export a`), + "Compile Error: unresolved reference 'a'\n\tat mod1:1:8") + + // runtime error within modules + expectError(t, ` +a := 1; +b := import("mod1"); +b(a)`, + Opts().Module("mod1", ` +export func(a) { + a() +} +`), "Runtime Error: not callable: int\n\tat mod1:3:4\n\tat test:4:1") + + // module skipping export + expectRun(t, `out = import("mod0")`, + Opts().Module("mod0", ``), tengo.UndefinedValue) + expectRun(t, `out = import("mod0")`, + Opts().Module("mod0", `if 1 { export true }`), true) + expectRun(t, `out = import("mod0")`, + Opts().Module("mod0", `if 0 { export true }`), + tengo.UndefinedValue) + expectRun(t, `out = import("mod0")`, + Opts().Module("mod0", `if 1 { } else { export true }`), + tengo.UndefinedValue) + expectRun(t, `out = import("mod0")`, + Opts().Module("mod0", `for v:=0;;v++ { if v == 3 { export true } } }`), + true) + expectRun(t, `out = import("mod0")`, + Opts().Module("mod0", `for v:=0;;v++ { if v == 3 { break } } }`), + tengo.UndefinedValue) +} + +func TestModuleBlockScopes(t *testing.T) { + m := Opts().Module("rand", + &tengo.BuiltinModule{ + Attrs: map[string]tengo.Object{ + "intn": &tengo.UserFunction{ + Name: "abs", + Value: func(a ...tengo.Object) (tengo.Object, error) { + v, _ := tengo.ToInt64(a[0]) + return &tengo.Int{Value: rand.Int63n(v)}, nil + }, + }, + }, + }) + + // block scopes in module + expectRun(t, `out = import("mod1")()`, m.Module( + "mod1", ` + rand := import("rand") + foo := func() { return 1 } + export func() { + rand.intn(3) + return foo() + }`), 1) + + expectRun(t, `out = import("mod1")()`, m.Module( + "mod1", ` +rand := import("rand") +foo := func() { return 1 } +export func() { + rand.intn(3) + if foo() {} + return 10 +} +`), 10) + + expectRun(t, `out = import("mod1")()`, m.Module( + "mod1", ` + rand := import("rand") + foo := func() { return 1 } + export func() { + rand.intn(3) + if true { foo() } + return 10 + } + `), 10) +} + +func TestBangOperator(t *testing.T) { + expectRun(t, `out = !true`, nil, false) + expectRun(t, `out = !false`, nil, true) + expectRun(t, `out = !0`, nil, true) + expectRun(t, `out = !5`, nil, false) + expectRun(t, `out = !!true`, nil, true) + expectRun(t, `out = !!false`, nil, false) + expectRun(t, `out = !!5`, nil, true) +} + +func TestObjectsLimit(t *testing.T) { + testAllocsLimit(t, `5`, 0) + testAllocsLimit(t, `5 + 5`, 1) + testAllocsLimit(t, `a := [1, 2, 3]`, 1) + testAllocsLimit(t, `a := 1; b := 2; c := 3; d := [a, b, c]`, 1) + testAllocsLimit(t, `a := {foo: 1, bar: 2}`, 1) + testAllocsLimit(t, `a := 1; b := 2; c := {foo: a, bar: b}`, 1) + testAllocsLimit(t, ` +f := func() { + return 5 + 5 +} +a := f() + 5 +`, 2) + testAllocsLimit(t, ` +f := func() { + return 5 + 5 +} +a := f() +`, 1) + testAllocsLimit(t, ` +a := [] +f := func() { + a = append(a, 5) +} +f() +f() +f() +`, 4) +} + +func testAllocsLimit(t *testing.T, src string, limit int64) { + expectRun(t, src, + Opts().Skip2ndPass(), tengo.UndefinedValue) // no limit + expectRun(t, src, + Opts().MaxAllocs(limit).Skip2ndPass(), tengo.UndefinedValue) + expectRun(t, src, + Opts().MaxAllocs(limit+1).Skip2ndPass(), tengo.UndefinedValue) + if limit > 1 { + expectError(t, src, + Opts().MaxAllocs(limit-1).Skip2ndPass(), + "allocation limit exceeded") + } + if limit > 2 { + expectError(t, src, + Opts().MaxAllocs(limit-2).Skip2ndPass(), + "allocation limit exceeded") + } +} + +func TestReturn(t *testing.T) { + expectRun(t, `out = func() { return 10; }()`, nil, 10) + expectRun(t, `out = func() { return 10; return 9; }()`, nil, 10) + expectRun(t, `out = func() { return 2 * 5; return 9 }()`, nil, 10) + expectRun(t, `out = func() { 9; return 2 * 5; return 9 }()`, nil, 10) + expectRun(t, ` + out = func() { + if (10 > 1) { + if (10 > 1) { + return 10; + } + + return 1; + } + }()`, nil, 10) + + expectRun(t, `f1 := func() { return 2 * 5; }; out = f1()`, nil, 10) +} + +func TestVMScopes(t *testing.T) { + // shadowed global variable + expectRun(t, ` +c := 5 +if a := 3; a { + c := 6 +} else { + c := 7 +} +out = c +`, nil, 5) + + // shadowed local variable + expectRun(t, ` +func() { + c := 5 + if a := 3; a { + c := 6 + } else { + c := 7 + } + out = c +}() +`, nil, 5) + + // 'b' is declared in 2 separate blocks + expectRun(t, ` +c := 5 +if a := 3; a { + b := 8 + c = b +} else { + b := 9 + c = b +} +out = c +`, nil, 8) + + // shadowing inside for statement + expectRun(t, ` +a := 4 +b := 5 +for i:=0;i<3;i++ { + b := 6 + for j:=0;j<2;j++ { + b := 7 + a = i*j + } +} +out = a`, nil, 2) + + // shadowing variable declared in init statement + expectRun(t, ` +if a := 5; a { + a := 6 + out = a +}`, nil, 6) + expectRun(t, ` +a := 4 +if a := 5; a { + a := 6 + out = a +}`, nil, 6) + expectRun(t, ` +a := 4 +if a := 0; a { + a := 6 + out = a +} else { + a := 7 + out = a +}`, nil, 7) + expectRun(t, ` +a := 4 +if a := 0; a { + out = a +} else { + out = a +}`, nil, 0) + + // shadowing function level + expectRun(t, ` +a := 5 +func() { + a := 6 + a = 7 +}() +out = a +`, nil, 5) + expectRun(t, ` +a := 5 +func() { + if a := 7; true { + a = 8 + } +}() +out = a +`, nil, 5) +} + +func TestSelector(t *testing.T) { + expectRun(t, `a := {k1: 5, k2: "foo"}; out = a.k1`, + nil, 5) + expectRun(t, `a := {k1: 5, k2: "foo"}; out = a.k2`, + nil, "foo") + expectRun(t, `a := {k1: 5, k2: "foo"}; out = a.k3`, + nil, tengo.UndefinedValue) + + expectRun(t, ` +a := { + b: { + c: 4, + a: false + }, + c: "foo bar" +} +out = a.b.c`, nil, 4) + + expectRun(t, ` +a := { + b: { + c: 4, + a: false + }, + c: "foo bar" +} +b := a.x.c`, nil, tengo.UndefinedValue) + + expectRun(t, ` +a := { + b: { + c: 4, + a: false + }, + c: "foo bar" +} +b := a.x.y`, nil, tengo.UndefinedValue) + + expectRun(t, `a := {b: 1, c: "foo"}; a.b = 2; out = a.b`, + nil, 2) + expectRun(t, `a := {b: 1, c: "foo"}; a.c = 2; out = a.c`, + nil, 2) // type not checked on sub-field + expectRun(t, `a := {b: {c: 1}}; a.b.c = 2; out = a.b.c`, + nil, 2) + expectRun(t, `a := {b: 1}; a.c = 2; out = a`, + nil, MAP{"b": 1, "c": 2}) + expectRun(t, `a := {b: {c: 1}}; a.b.d = 2; out = a`, + nil, MAP{"b": MAP{"c": 1, "d": 2}}) + + expectRun(t, `func() { a := {b: 1, c: "foo"}; a.b = 2; out = a.b }()`, + nil, 2) + expectRun(t, `func() { a := {b: 1, c: "foo"}; a.c = 2; out = a.c }()`, + nil, 2) // type not checked on sub-field + expectRun(t, `func() { a := {b: {c: 1}}; a.b.c = 2; out = a.b.c }()`, + nil, 2) + expectRun(t, `func() { a := {b: 1}; a.c = 2; out = a }()`, + nil, MAP{"b": 1, "c": 2}) + expectRun(t, `func() { a := {b: {c: 1}}; a.b.d = 2; out = a }()`, + nil, MAP{"b": MAP{"c": 1, "d": 2}}) + + expectRun(t, `func() { a := {b: 1, c: "foo"}; func() { a.b = 2 }(); out = a.b }()`, + nil, 2) + expectRun(t, `func() { a := {b: 1, c: "foo"}; func() { a.c = 2 }(); out = a.c }()`, + nil, 2) // type not checked on sub-field + expectRun(t, `func() { a := {b: {c: 1}}; func() { a.b.c = 2 }(); out = a.b.c }()`, + nil, 2) + expectRun(t, `func() { a := {b: 1}; func() { a.c = 2 }(); out = a }()`, + nil, MAP{"b": 1, "c": 2}) + expectRun(t, `func() { a := {b: {c: 1}}; func() { a.b.d = 2 }(); out = a }()`, + nil, MAP{"b": MAP{"c": 1, "d": 2}}) + + expectRun(t, ` +a := { + b: [1, 2, 3], + c: { + d: 8, + e: "foo", + f: [9, 8] + } +} +out = [a.b[2], a.c.d, a.c.e, a.c.f[1]] +`, nil, ARR{3, 8, "foo", 8}) + + expectRun(t, ` +func() { + a := [1, 2, 3] + b := 9 + a[1] = b + b = 7 // make sure a[1] has a COPY of value of 'b' + out = a[1] +}() +`, nil, 9) + + expectError(t, `a := {b: {c: 1}}; a.d.c = 2`, + nil, "not index-assignable") + expectError(t, `a := [1, 2, 3]; a.b = 2`, + nil, "invalid index type") + expectError(t, `a := "foo"; a.b = 2`, + nil, "not index-assignable") + expectError(t, `func() { a := {b: {c: 1}}; a.d.c = 2 }()`, + nil, "not index-assignable") + expectError(t, `func() { a := [1, 2, 3]; a.b = 2 }()`, + nil, "invalid index type") + expectError(t, `func() { a := "foo"; a.b = 2 }()`, + nil, "not index-assignable") +} + +func TestSourceModules(t *testing.T) { + testEnumModule(t, `out = enum.key(0, 20)`, 0) + testEnumModule(t, `out = enum.key(10, 20)`, 10) + testEnumModule(t, `out = enum.value(0, 0)`, 0) + testEnumModule(t, `out = enum.value(10, 20)`, 20) + + testEnumModule(t, `out = enum.all([], enum.value)`, true) + testEnumModule(t, `out = enum.all([1], enum.value)`, true) + testEnumModule(t, `out = enum.all([true, 1], enum.value)`, true) + testEnumModule(t, `out = enum.all([true, 0], enum.value)`, false) + testEnumModule(t, `out = enum.all([true, 0, 1], enum.value)`, false) + testEnumModule(t, `out = enum.all(immutable([true, 0, 1]), enum.value)`, + false) // immutable-array + testEnumModule(t, `out = enum.all({}, enum.value)`, true) + testEnumModule(t, `out = enum.all({a:1}, enum.value)`, true) + testEnumModule(t, `out = enum.all({a:true, b:1}, enum.value)`, true) + testEnumModule(t, `out = enum.all(immutable({a:true, b:1}), enum.value)`, + true) // immutable-map + testEnumModule(t, `out = enum.all({a:true, b:0}, enum.value)`, false) + testEnumModule(t, `out = enum.all({a:true, b:0, c:1}, enum.value)`, false) + testEnumModule(t, `out = enum.all(0, enum.value)`, + tengo.UndefinedValue) // non-enumerable: undefined + testEnumModule(t, `out = enum.all("123", enum.value)`, + tengo.UndefinedValue) // non-enumerable: undefined + + testEnumModule(t, `out = enum.any([], enum.value)`, false) + testEnumModule(t, `out = enum.any([1], enum.value)`, true) + testEnumModule(t, `out = enum.any([true, 1], enum.value)`, true) + testEnumModule(t, `out = enum.any([true, 0], enum.value)`, true) + testEnumModule(t, `out = enum.any([true, 0, 1], enum.value)`, true) + testEnumModule(t, `out = enum.any(immutable([true, 0, 1]), enum.value)`, + true) // immutable-array + testEnumModule(t, `out = enum.any([false], enum.value)`, false) + testEnumModule(t, `out = enum.any([false, 0], enum.value)`, false) + testEnumModule(t, `out = enum.any({}, enum.value)`, false) + testEnumModule(t, `out = enum.any({a:1}, enum.value)`, true) + testEnumModule(t, `out = enum.any({a:true, b:1}, enum.value)`, true) + testEnumModule(t, `out = enum.any({a:true, b:0}, enum.value)`, true) + testEnumModule(t, `out = enum.any({a:true, b:0, c:1}, enum.value)`, true) + testEnumModule(t, `out = enum.any(immutable({a:true, b:0, c:1}), enum.value)`, + true) // immutable-map + testEnumModule(t, `out = enum.any({a:false}, enum.value)`, false) + testEnumModule(t, `out = enum.any({a:false, b:0}, enum.value)`, false) + testEnumModule(t, `out = enum.any(0, enum.value)`, + tengo.UndefinedValue) // non-enumerable: undefined + testEnumModule(t, `out = enum.any("123", enum.value)`, + tengo.UndefinedValue) // non-enumerable: undefined + + testEnumModule(t, `out = enum.chunk([], 1)`, ARR{}) + testEnumModule(t, `out = enum.chunk([1], 1)`, ARR{ARR{1}}) + testEnumModule(t, `out = enum.chunk([1,2,3], 1)`, + ARR{ARR{1}, ARR{2}, ARR{3}}) + testEnumModule(t, `out = enum.chunk([1,2,3], 2)`, + ARR{ARR{1, 2}, ARR{3}}) + testEnumModule(t, `out = enum.chunk([1,2,3], 3)`, + ARR{ARR{1, 2, 3}}) + testEnumModule(t, `out = enum.chunk([1,2,3], 4)`, + ARR{ARR{1, 2, 3}}) + testEnumModule(t, `out = enum.chunk([1,2,3,4], 3)`, + ARR{ARR{1, 2, 3}, ARR{4}}) + testEnumModule(t, `out = enum.chunk([], 0)`, + tengo.UndefinedValue) // size=0: undefined + testEnumModule(t, `out = enum.chunk([1], 0)`, + tengo.UndefinedValue) // size=0: undefined + testEnumModule(t, `out = enum.chunk([1,2,3], 0)`, + tengo.UndefinedValue) // size=0: undefined + testEnumModule(t, `out = enum.chunk({a:1,b:2,c:3}, 1)`, + tengo.UndefinedValue) // map: undefined + testEnumModule(t, `out = enum.chunk(0, 1)`, + tengo.UndefinedValue) // non-enumerable: undefined + testEnumModule(t, `out = enum.chunk("123", 1)`, + tengo.UndefinedValue) // non-enumerable: undefined + + testEnumModule(t, `out = enum.at([], 0)`, + tengo.UndefinedValue) + testEnumModule(t, `out = enum.at([], 1)`, + tengo.UndefinedValue) + testEnumModule(t, `out = enum.at([], -1)`, + tengo.UndefinedValue) + testEnumModule(t, `out = enum.at(["one"], 0)`, + "one") + testEnumModule(t, `out = enum.at(["one"], 1)`, + tengo.UndefinedValue) + testEnumModule(t, `out = enum.at(["one"], -1)`, + tengo.UndefinedValue) + testEnumModule(t, `out = enum.at(["one","two","three"], 0)`, + "one") + testEnumModule(t, `out = enum.at(["one","two","three"], 1)`, + "two") + testEnumModule(t, `out = enum.at(["one","two","three"], 2)`, + "three") + testEnumModule(t, `out = enum.at(["one","two","three"], -1)`, + tengo.UndefinedValue) + testEnumModule(t, `out = enum.at(["one","two","three"], 3)`, + tengo.UndefinedValue) + testEnumModule(t, `out = enum.at(["one","two","three"], "1")`, + tengo.UndefinedValue) // non-int index: undefined + testEnumModule(t, `out = enum.at({}, "a")`, + tengo.UndefinedValue) + testEnumModule(t, `out = enum.at({a:"one"}, "a")`, + "one") + testEnumModule(t, `out = enum.at({a:"one"}, "b")`, + tengo.UndefinedValue) + testEnumModule(t, `out = enum.at({a:"one",b:"two",c:"three"}, "a")`, + "one") + testEnumModule(t, `out = enum.at({a:"one",b:"two",c:"three"}, "b")`, + "two") + testEnumModule(t, `out = enum.at({a:"one",b:"two",c:"three"}, "c")`, + "three") + testEnumModule(t, `out = enum.at({a:"one",b:"two",c:"three"}, "d")`, + tengo.UndefinedValue) + testEnumModule(t, `out = enum.at({a:"one",b:"two",c:"three"}, 'a')`, + tengo.UndefinedValue) // non-string index: undefined + testEnumModule(t, `out = enum.at(0, 1)`, + tengo.UndefinedValue) // non-enumerable: undefined + testEnumModule(t, `out = enum.at("abc", 1)`, + tengo.UndefinedValue) // non-enumerable: undefined + + testEnumModule(t, `out=0; enum.each([],func(k,v){out+=v})`, 0) + testEnumModule(t, `out=0; enum.each([1,2,3],func(k,v){out+=v})`, 6) + testEnumModule(t, `out=0; enum.each([1,2,3],func(k,v){out+=k})`, 3) + testEnumModule(t, `out=0; enum.each({a:1,b:2,c:3},func(k,v){out+=v})`, 6) + testEnumModule(t, `out=""; enum.each({a:1,b:2,c:3},func(k,v){out+=k}); out=len(out)`, + 3) + testEnumModule(t, `out=0; enum.each(5,func(k,v){out+=v})`, 0) // non-enumerable: no iteration + testEnumModule(t, `out=0; enum.each("123",func(k,v){out+=v})`, 0) // non-enumerable: no iteration + + testEnumModule(t, `out = enum.filter([], enum.value)`, + ARR{}) + testEnumModule(t, `out = enum.filter([false,1,2], enum.value)`, + ARR{1, 2}) + testEnumModule(t, `out = enum.filter([false,1,0,2], enum.value)`, + ARR{1, 2}) + testEnumModule(t, `out = enum.filter({}, enum.value)`, + tengo.UndefinedValue) // non-array: undefined + testEnumModule(t, `out = enum.filter(0, enum.value)`, + tengo.UndefinedValue) // non-array: undefined + testEnumModule(t, `out = enum.filter("123", enum.value)`, + tengo.UndefinedValue) // non-array: undefined + + testEnumModule(t, `out = enum.find([], enum.value)`, + tengo.UndefinedValue) + testEnumModule(t, `out = enum.find([0], enum.value)`, + tengo.UndefinedValue) + testEnumModule(t, `out = enum.find([1], enum.value)`, 1) + testEnumModule(t, `out = enum.find([false,0,undefined,1], enum.value)`, 1) + testEnumModule(t, `out = enum.find([1,2,3], enum.value)`, 1) + testEnumModule(t, `out = enum.find({}, enum.value)`, + tengo.UndefinedValue) + testEnumModule(t, `out = enum.find({a:0}, enum.value)`, + tengo.UndefinedValue) + testEnumModule(t, `out = enum.find({a:1}, enum.value)`, 1) + testEnumModule(t, `out = enum.find({a:false,b:0,c:undefined,d:1}, enum.value)`, + 1) + //testEnumModule(t, `out = enum.find({a:1,b:2,c:3}, enum.value)`, 1) + testEnumModule(t, `out = enum.find(0, enum.value)`, + tengo.UndefinedValue) // non-enumerable: undefined + testEnumModule(t, `out = enum.find("123", enum.value)`, + tengo.UndefinedValue) // non-enumerable: undefined + + testEnumModule(t, `out = enum.find_key([], enum.value)`, + tengo.UndefinedValue) + testEnumModule(t, `out = enum.find_key([0], enum.value)`, + tengo.UndefinedValue) + testEnumModule(t, `out = enum.find_key([1], enum.value)`, 0) + testEnumModule(t, `out = enum.find_key([false,0,undefined,1], enum.value)`, + 3) + testEnumModule(t, `out = enum.find_key([1,2,3], enum.value)`, 0) + testEnumModule(t, `out = enum.find_key({}, enum.value)`, + tengo.UndefinedValue) + testEnumModule(t, `out = enum.find_key({a:0}, enum.value)`, + tengo.UndefinedValue) + testEnumModule(t, `out = enum.find_key({a:1}, enum.value)`, + "a") + testEnumModule(t, `out = enum.find_key({a:false,b:0,c:undefined,d:1}, enum.value)`, + "d") + //testEnumModule(t, `out = enum.find_key({a:1,b:2,c:3}, enum.value)`, "a") + testEnumModule(t, `out = enum.find_key(0, enum.value)`, + tengo.UndefinedValue) // non-enumerable: undefined + testEnumModule(t, `out = enum.find_key("123", enum.value)`, + tengo.UndefinedValue) // non-enumerable: undefined + + testEnumModule(t, `out = enum.map([], enum.value)`, + ARR{}) + testEnumModule(t, `out = enum.map([1,2,3], enum.value)`, + ARR{1, 2, 3}) + testEnumModule(t, `out = enum.map([1,2,3], enum.key)`, + ARR{0, 1, 2}) + testEnumModule(t, `out = enum.map([1,2,3], func(k,v) { return v*2 })`, + ARR{2, 4, 6}) + testEnumModule(t, `out = enum.map({}, enum.value)`, + ARR{}) + testEnumModule(t, `out = enum.map({a:1}, func(k,v) { return v*2 })`, + ARR{2}) + testEnumModule(t, `out = enum.map(0, enum.value)`, + tengo.UndefinedValue) // non-enumerable: undefined + testEnumModule(t, `out = enum.map("123", enum.value)`, + tengo.UndefinedValue) // non-enumerable: undefined +} + +func testEnumModule(t *testing.T, input string, expected interface{}) { + expectRun(t, `enum := import("enum"); `+input, + Opts().Module("enum", stdlib.SourceModules["enum"]), + expected) +} + +func TestSrcModEnum(t *testing.T) { + expectRun(t, ` +x := import("enum") +out = x.all([1, 2, 3], func(_, v) { return v >= 1 }) +`, Opts().Stdlib(), true) + expectRun(t, ` +x := import("enum") +out = x.all([1, 2, 3], func(_, v) { return v >= 2 }) +`, Opts().Stdlib(), false) + + expectRun(t, ` +x := import("enum") +out = x.any([1, 2, 3], func(_, v) { return v >= 1 }) +`, Opts().Stdlib(), true) + expectRun(t, ` +x := import("enum") +out = x.any([1, 2, 3], func(_, v) { return v >= 2 }) +`, Opts().Stdlib(), true) + + expectRun(t, ` +x := import("enum") +out = x.chunk([1, 2, 3], 1) +`, Opts().Stdlib(), ARR{ARR{1}, ARR{2}, ARR{3}}) + expectRun(t, ` +x := import("enum") +out = x.chunk([1, 2, 3], 2) +`, Opts().Stdlib(), ARR{ARR{1, 2}, ARR{3}}) + expectRun(t, ` +x := import("enum") +out = x.chunk([1, 2, 3], 3) +`, Opts().Stdlib(), ARR{ARR{1, 2, 3}}) + expectRun(t, ` +x := import("enum") +out = x.chunk([1, 2, 3], 4) +`, Opts().Stdlib(), ARR{ARR{1, 2, 3}}) + expectRun(t, ` +x := import("enum") +out = x.chunk([1, 2, 3, 4, 5, 6], 2) +`, Opts().Stdlib(), ARR{ARR{1, 2}, ARR{3, 4}, ARR{5, 6}}) + + expectRun(t, ` +x := import("enum") +out = x.at([1, 2, 3], 0) +`, Opts().Stdlib(), 1) +} + +func TestVMStackOverflow(t *testing.T) { + expectError(t, `f := func() { return f() + 1 }; f()`, + nil, "stack overflow") +} + +func TestString(t *testing.T) { + expectRun(t, `out = "Hello World!"`, nil, "Hello World!") + expectRun(t, `out = "Hello" + " " + "World!"`, nil, "Hello World!") + + expectRun(t, `out = "Hello" == "Hello"`, nil, true) + expectRun(t, `out = "Hello" == "World"`, nil, false) + expectRun(t, `out = "Hello" != "Hello"`, nil, false) + expectRun(t, `out = "Hello" != "World"`, nil, true) + + // index operator + str := "abcdef" + strStr := `"abcdef"` + strLen := 6 + for idx := 0; idx < strLen; idx++ { + expectRun(t, fmt.Sprintf("out = %s[%d]", strStr, idx), + nil, str[idx]) + expectRun(t, fmt.Sprintf("out = %s[0 + %d]", strStr, idx), + nil, str[idx]) + expectRun(t, fmt.Sprintf("out = %s[1 + %d - 1]", strStr, idx), + nil, str[idx]) + expectRun(t, fmt.Sprintf("idx := %d; out = %s[idx]", idx, strStr), + nil, str[idx]) + } + + expectRun(t, fmt.Sprintf("%s[%d]", strStr, -1), + nil, tengo.UndefinedValue) + expectRun(t, fmt.Sprintf("%s[%d]", strStr, strLen), + nil, tengo.UndefinedValue) + + // slice operator + for low := 0; low <= strLen; low++ { + expectRun(t, fmt.Sprintf("out = %s[%d:%d]", strStr, low, low), + nil, "") + for high := low; high <= strLen; high++ { + expectRun(t, fmt.Sprintf("out = %s[%d:%d]", strStr, low, high), + nil, str[low:high]) + expectRun(t, + fmt.Sprintf("out = %s[0 + %d : 0 + %d]", strStr, low, high), + nil, str[low:high]) + expectRun(t, + fmt.Sprintf("out = %s[1 + %d - 1 : 1 + %d - 1]", + strStr, low, high), + nil, str[low:high]) + expectRun(t, + fmt.Sprintf("out = %s[:%d]", strStr, high), + nil, str[:high]) + expectRun(t, + fmt.Sprintf("out = %s[%d:]", strStr, low), + nil, str[low:]) + } + } + + expectRun(t, fmt.Sprintf("out = %s[:]", strStr), + nil, str[:]) + expectRun(t, fmt.Sprintf("out = %s[:]", strStr), + nil, str) + expectRun(t, fmt.Sprintf("out = %s[%d:]", strStr, -1), + nil, str) + expectRun(t, fmt.Sprintf("out = %s[:%d]", strStr, strLen+1), + nil, str) + expectRun(t, fmt.Sprintf("out = %s[%d:%d]", strStr, 2, 2), + nil, "") + + expectError(t, fmt.Sprintf("%s[:%d]", strStr, -1), + nil, "invalid slice index") + expectError(t, fmt.Sprintf("%s[%d:]", strStr, strLen+1), + nil, "invalid slice index") + expectError(t, fmt.Sprintf("%s[%d:%d]", strStr, 0, -1), + nil, "invalid slice index") + expectError(t, fmt.Sprintf("%s[%d:%d]", strStr, 2, 1), + nil, "invalid slice index") + + // string concatenation with other types + expectRun(t, `out = "foo" + 1`, nil, "foo1") + // Float.String() returns the smallest number of digits + // necessary such that ParseFloat will return f exactly. + expectRun(t, `out = "foo" + 1.0`, nil, "foo1") // <- note '1' instead of '1.0' + expectRun(t, `out = "foo" + 1.5`, nil, "foo1.5") + expectRun(t, `out = "foo" + true`, nil, "footrue") + expectRun(t, `out = "foo" + 'X'`, nil, "fooX") + expectRun(t, `out = "foo" + error(5)`, nil, "fooerror: 5") + expectRun(t, `out = "foo" + undefined`, nil, "foo") + expectRun(t, `out = "foo" + [1,2,3]`, nil, "foo[1, 2, 3]") + // also works with "+=" operator + expectRun(t, `out = "foo"; out += 1.5`, nil, "foo1.5") + // string concats works only when string is LHS + expectError(t, `1 + "foo"`, nil, "invalid operation") + + expectError(t, `"foo" - "bar"`, nil, "invalid operation") +} + +func TestTailCall(t *testing.T) { + expectRun(t, ` + fac := func(n, a) { + if n == 1 { + return a + } + return fac(n-1, n*a) + } + out = fac(5, 1)`, nil, 120) + + expectRun(t, ` + fac := func(n, a) { + if n == 1 { + return a + } + x := {foo: fac} // indirection for test + return x.foo(n-1, n*a) + } + out = fac(5, 1)`, nil, 120) + + expectRun(t, ` + fib := func(x, s) { + if x == 0 { + return 0 + s + } else if x == 1 { + return 1 + s + } + return fib(x-1, fib(x-2, s)) + } + out = fib(15, 0)`, nil, 610) + + expectRun(t, ` + fib := func(n, a, b) { + if n == 0 { + return a + } else if n == 1 { + return b + } + return fib(n-1, b, a + b) + } + out = fib(15, 0, 1)`, nil, 610) + + // global variable and no return value + expectRun(t, ` + out = 0 + foo := func(a) { + if a == 0 { + return + } + out += a + foo(a-1) + } + foo(10)`, nil, 55) + + expectRun(t, ` + f1 := func() { + f2 := 0 // TODO: this might be fixed in the future + f2 = func(n, s) { + if n == 0 { return s } + return f2(n-1, n + s) + } + return f2(5, 0) + } + out = f1()`, nil, 15) + + // tail-call replacing loop + // without tail-call optimization, this code will cause stack overflow + expectRun(t, ` +iter := func(n, max) { + if n == max { + return n + } + + return iter(n+1, max) +} +out = iter(0, 9999) +`, nil, 9999) + expectRun(t, ` +c := 0 +iter := func(n, max) { + if n == max { + return + } + + c++ + iter(n+1, max) +} +iter(0, 9999) +out = c +`, nil, 9999) +} + +// tail call with free vars +func TestTailCallFreeVars(t *testing.T) { + expectRun(t, ` +func() { + a := 10 + f2 := 0 + f2 = func(n, s) { + if n == 0 { + return s + a + } + return f2(n-1, n+s) + } + out = f2(5, 0) +}()`, nil, 25) +} + +func expectRun( + t *testing.T, + input string, + opts *testopts, + expected interface{}, +) { + if opts == nil { + opts = Opts() + } + + symbols := opts.symbols + modules := opts.modules + maxAllocs := opts.maxAllocs + + expectedObj := toObject(expected) + + if symbols == nil { + symbols = make(map[string]tengo.Object) + } + symbols[testOut] = objectZeroCopy(expectedObj) + + // first pass: run the code normally + { + // parse + file := parse(t, input) + if file == nil { + return + } + + // compiler/VM + res, trace, err := traceCompileRun(file, symbols, modules, maxAllocs) + require.NoError(t, err, "\n"+strings.Join(trace, "\n")) + require.Equal(t, expectedObj, res[testOut], + "\n"+strings.Join(trace, "\n")) + } + + // second pass: run the code as import module + if !opts.skip2ndPass { + file := parse(t, `out = import("__code__")`) + if file == nil { + return + } + + expectedObj := toObject(expected) + switch eo := expectedObj.(type) { + case *tengo.Array: + expectedObj = &tengo.ImmutableArray{Value: eo.Value} + case *tengo.Map: + expectedObj = &tengo.ImmutableMap{Value: eo.Value} + } + + modules.AddSourceModule("__code__", + []byte(fmt.Sprintf("out := undefined; %s; export out", input))) + + res, trace, err := traceCompileRun(file, symbols, modules, maxAllocs) + require.NoError(t, err, "\n"+strings.Join(trace, "\n")) + require.Equal(t, expectedObj, res[testOut], + "\n"+strings.Join(trace, "\n")) + } +} + +func expectError( + t *testing.T, + input string, + opts *testopts, + expected string, +) { + if opts == nil { + opts = Opts() + } + + symbols := opts.symbols + modules := opts.modules + maxAllocs := opts.maxAllocs + + expected = strings.TrimSpace(expected) + if expected == "" { + panic("expected must not be empty") + } + + // parse + program := parse(t, input) + if program == nil { + return + } + + // compiler/VM + _, trace, err := traceCompileRun(program, symbols, modules, maxAllocs) + require.Error(t, err, "\n"+strings.Join(trace, "\n")) + require.True(t, strings.Contains(err.Error(), expected), + "expected error string: %s, got: %s\n%s", + expected, err.Error(), strings.Join(trace, "\n")) +} + +type vmTracer struct { + Out []string +} + +func (o *vmTracer) Write(p []byte) (n int, err error) { + o.Out = append(o.Out, string(p)) + return len(p), nil +} + +func traceCompileRun( + file *internal.File, + symbols map[string]tengo.Object, + modules *tengo.ModuleMap, + maxAllocs int64, +) (res map[string]tengo.Object, trace []string, err error) { + var v *tengo.VM + + defer func() { + if e := recover(); e != nil { + err = fmt.Errorf("panic: %v", e) + + // stack trace + var stackTrace []string + for i := 2; ; i += 1 { + _, file, line, ok := _runtime.Caller(i) + if !ok { + break + } + stackTrace = append(stackTrace, + fmt.Sprintf(" %s:%d", file, line)) + } + + trace = append(trace, + fmt.Sprintf("[Error Trace]\n\n %s\n", + strings.Join(stackTrace, "\n "))) + } + }() + + globals := make([]tengo.Object, tengo.GlobalsSize) + + symTable := internal.NewSymbolTable() + for name, value := range symbols { + sym := symTable.Define(name) + + // should not store pointer to 'value' variable + // which is re-used in each iteration. + valueCopy := value + globals[sym.Index] = valueCopy + } + for idx, fn := range tengo.GetAllBuiltinFunctions() { + symTable.DefineBuiltin(idx, fn.Name) + } + + tr := &vmTracer{} + c := tengo.NewCompiler(file.InputFile, symTable, nil, modules, tr) + err = c.Compile(file) + trace = append(trace, + fmt.Sprintf("\n[Compiler Trace]\n\n%s", + strings.Join(tr.Out, ""))) + if err != nil { + return + } + + bytecode := c.Bytecode() + bytecode.RemoveDuplicates() + trace = append(trace, fmt.Sprintf("\n[Compiled Constants]\n\n%s", + strings.Join(bytecode.FormatConstants(), "\n"))) + trace = append(trace, fmt.Sprintf("\n[Compiled Instructions]\n\n%s\n", + strings.Join(bytecode.FormatInstructions(), "\n"))) + + v = tengo.NewVM(bytecode, globals, maxAllocs) + + err = v.Run() + { + res = make(map[string]tengo.Object) + for name := range symbols { + sym, depth, ok := symTable.Resolve(name) + if !ok || depth != 0 { + err = fmt.Errorf("symbol not found: %s", name) + return + } + + res[name] = globals[sym.Index] + } + trace = append(trace, fmt.Sprintf("\n[Globals]\n\n%s", + strings.Join(formatGlobals(globals), "\n"))) + } + if err == nil && !v.IsStackEmpty() { + err = errors.New("non empty stack after execution") + } + + return +} + +func formatGlobals(globals []tengo.Object) (formatted []string) { + for idx, global := range globals { + if global == nil { + return + } + formatted = append(formatted, fmt.Sprintf("[% 3d] %s (%s|%p)", + idx, global.String(), reflect.TypeOf(global).Elem().Name(), global)) + } + return +} + +func parse(t *testing.T, input string) *internal.File { + testFileSet := internal.NewFileSet() + testFile := testFileSet.AddFile("test", -1, len(input)) + + p := internal.NewParser(testFile, []byte(input), nil) + file, err := p.ParseFile() + require.NoError(t, err) + return file +} + +func errorObject(v interface{}) *tengo.Error { + return &tengo.Error{Value: toObject(v)} +} + +func toObject(v interface{}) tengo.Object { + switch v := v.(type) { + case tengo.Object: + return v + case string: + return &tengo.String{Value: v} + case int64: + return &tengo.Int{Value: v} + case int: // for convenience + return &tengo.Int{Value: int64(v)} + case bool: + if v { + return tengo.TrueValue + } + return tengo.FalseValue + case rune: + return &tengo.Char{Value: v} + case byte: // for convenience + return &tengo.Char{Value: rune(v)} + case float64: + return &tengo.Float{Value: v} + case []byte: + return &tengo.Bytes{Value: v} + case MAP: + objs := make(map[string]tengo.Object) + for k, v := range v { + objs[k] = toObject(v) + } + + return &tengo.Map{Value: objs} + case ARR: + var objs []tengo.Object + for _, e := range v { + objs = append(objs, toObject(e)) + } + + return &tengo.Array{Value: objs} + case IMAP: + objs := make(map[string]tengo.Object) + for k, v := range v { + objs[k] = toObject(v) + } + + return &tengo.ImmutableMap{Value: objs} + case IARR: + var objs []tengo.Object + for _, e := range v { + objs = append(objs, toObject(e)) + } + + return &tengo.ImmutableArray{Value: objs} + } + + panic(fmt.Errorf("unknown type: %T", v)) +} + +func objectZeroCopy(o tengo.Object) tengo.Object { + switch o.(type) { + case *tengo.Int: + return &tengo.Int{} + case *tengo.Float: + return &tengo.Float{} + case *tengo.Bool: + return &tengo.Bool{} + case *tengo.Char: + return &tengo.Char{} + case *tengo.String: + return &tengo.String{} + case *tengo.Array: + return &tengo.Array{} + case *tengo.Map: + return &tengo.Map{} + case *tengo.Undefined: + return tengo.UndefinedValue + case *tengo.Error: + return &tengo.Error{} + case *tengo.Bytes: + return &tengo.Bytes{} + case *tengo.ImmutableArray: + return &tengo.ImmutableArray{} + case *tengo.ImmutableMap: + return &tengo.ImmutableMap{} + case nil: + panic("nil") + default: + panic(fmt.Errorf("unknown object type: %s", o.TypeName())) + } +}