diff --git a/be/CMakeLists.txt b/be/CMakeLists.txt index faef51dd1e2..a2d770edb50 100644 --- a/be/CMakeLists.txt +++ b/be/CMakeLists.txt @@ -614,6 +614,7 @@ set(STARROCKS_LINK_LIBS Types Udf Util + Script StarRocksGen Webserver TestUtil @@ -861,6 +862,7 @@ add_subdirectory(${SRC_DIR}/udf) add_subdirectory(${SRC_DIR}/tools) add_subdirectory(${SRC_DIR}/util) +add_subdirectory(${SRC_DIR}/script) if (WITH_BENCH STREQUAL "ON") message(STATUS "Build binary with bench") diff --git a/be/src/runtime/command_executor.cpp b/be/src/runtime/command_executor.cpp index 777fea5b261..0ece8248e37 100644 --- a/be/src/runtime/command_executor.cpp +++ b/be/src/runtime/command_executor.cpp @@ -19,6 +19,7 @@ #include "common/configbase.h" #include "gutil/strings/substitute.h" #include "http/action/update_config_action.h" +#include "script/script.h" namespace starrocks { @@ -41,10 +42,12 @@ Status handle_set_config(const string& params_str) { return update_config->update_config(name_itr->value.GetString(), value_itr->value.GetString()); } -Status execute_command(const std::string& command, const std::string& params) { - LOG(INFO) << "execute command: " << command << " params: " << params; +Status execute_command(const std::string& command, const std::string& params, std::string* result) { + LOG(INFO) << "execute command: " << command << " params: " << params.substr(0, 2000); if (command == "set_config") { return handle_set_config(params); + } else if (command == "execute_script") { + return execute_script(params, *result); } return Status::NotSupported(strings::Substitute("command $0 not supported", command)); } diff --git a/be/src/runtime/command_executor.h b/be/src/runtime/command_executor.h index d63347abe95..687337663d5 100644 --- a/be/src/runtime/command_executor.h +++ b/be/src/runtime/command_executor.h @@ -20,6 +20,6 @@ namespace starrocks { -Status execute_command(const std::string& command, const std::string& params); +Status execute_command(const std::string& command, const std::string& params, std::string* result); } // namespace starrocks \ No newline at end of file diff --git a/be/src/script/CMakeLists.txt b/be/src/script/CMakeLists.txt new file mode 100644 index 00000000000..adcf24f0fb4 --- /dev/null +++ b/be/src/script/CMakeLists.txt @@ -0,0 +1,26 @@ +# Copyright 2021-present StarRocks, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set(LIBRARY_OUTPUT_PATH "${BUILD_DIR}/src/script") + +set(EXECUTABLE_OUTPUT_PATH "${BUILD_DIR}/src/script") + +set(EXEC_FILES + script.cpp + ../thirdparty/wren/wren.c +) + +add_library(Script STATIC ${EXEC_FILES}) + +target_include_directories(Script PRIVATE ${SRC_DIR}/script ${SRC_DIR}/thirdparty/wren ${SRC_DIR}/thirdparty/wrenbind17) diff --git a/be/src/script/script.cpp b/be/src/script/script.cpp new file mode 100644 index 00000000000..b1a02fe8277 --- /dev/null +++ b/be/src/script/script.cpp @@ -0,0 +1,307 @@ +// Copyright 2021-present StarRocks, Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "script/script.h" + +#include + +#include "common/logging.h" +#include "exec/schema_scanner/schema_be_tablets_scanner.h" +#include "gen_cpp/olap_file.pb.h" +#include "gutil/strings/substitute.h" +#include "storage/storage_engine.h" +#include "storage/tablet.h" +#include "storage/tablet_manager.h" +#include "storage/tablet_updates.h" +#include "wrenbind17/wrenbind17.hpp" + +using namespace wrenbind17; +using std::string; + +namespace starrocks { + +#define REG_VAR(TYPE, NAME) cls.var<&TYPE::NAME>(#NAME) +#define REG_METHOD(TYPE, NAME) cls.func<&TYPE::NAME>(#NAME) +#define REG_STATIC_METHOD(TYPE, NAME) cls.funcStatic<&TYPE::NAME>(#NAME) + +template +std::string proto_to_json(T& proto) { + std::string json; + google::protobuf::util::MessageToJsonString(proto, &json); + return json; +} + +static std::shared_ptr tablet_updates_to_pb(TabletUpdates& self) { + std::shared_ptr pb = std::make_shared(); + self.to_updates_pb(pb.get()); + return pb; +} + +static EditVersionMetaPB* tablet_updates_pb_version(TabletUpdatesPB& self, int idx) { + if (idx < 0 || idx >= self.versions_size()) return nullptr; + return self.mutable_versions(idx); +} + +static uint64_t kv_store_get_live_sst_files_size(KVStore& store) { + uint64_t ret = 0; + store.get_live_sst_files_size(&ret); + return ret; +} + +static int tablet_keys_type_int(Tablet& tablet) { + return static_cast(tablet.keys_type()); +} + +static int tablet_tablet_state(Tablet& tablet) { + return static_cast(tablet.tablet_state()); +} + +static const TabletSchema& tablet_tablet_schema(Tablet& tablet) { + return tablet.tablet_schema(); +} + +static uint64_t tablet_tablet_id(Tablet& tablet) { + return tablet.tablet_id(); +} + +static std::string tablet_path(Tablet& tablet) { + return tablet.schema_hash_path(); +} + +static DataDir* tablet_data_dir(Tablet& tablet) { + return tablet.data_dir(); +} + +static uint64_t get_major(EditVersion& self) { + return self.major(); +} + +static uint64_t get_minor(EditVersion& self) { + return self.minor(); +} + +static void bind_common(ForeignModule& m) { + { + auto& cls = m.klass("Status"); + cls.func<&Status::to_string>("toString"); + REG_METHOD(Status, ok); + } +} + +class StorageEngineRef { +public: + static string drop_tablet(int64_t tablet_id) { + auto manager = StorageEngine::instance()->tablet_manager(); + string err; + auto ptr = manager->get_tablet(tablet_id, true, &err); + if (ptr == nullptr) { + return strings::Substitute("get tablet $0 failed: $1", tablet_id, err); + } + auto st = manager->drop_tablet(tablet_id, TabletDropFlag::kKeepMetaAndFiles); + return st.to_string(); + } + + static std::shared_ptr get_tablet(int64_t tablet_id) { + string err; + auto ptr = StorageEngine::instance()->tablet_manager()->get_tablet(tablet_id, true, &err); + if (ptr == nullptr) { + LOG(WARNING) << "get_tablet " << tablet_id << " failed: " << err; + return nullptr; + } + return ptr; + } + + static std::shared_ptr get_tablet_info(int64_t tablet_id) { + std::vector tablet_infos; + auto manager = StorageEngine::instance()->tablet_manager(); + manager->get_tablets_basic_infos(-1, -1, tablet_id, tablet_infos); + if (tablet_infos.empty()) { + return nullptr; + } else { + return std::make_shared(tablet_infos[0]); + } + } + + static std::vector get_tablet_infos(int64_t table_id, int64_t partition_id) { + std::vector tablet_infos; + auto manager = StorageEngine::instance()->tablet_manager(); + manager->get_tablets_basic_infos(table_id, partition_id, -1, tablet_infos); + return tablet_infos; + } + + static std::vector get_data_dirs() { return StorageEngine::instance()->get_stores(); } + + static void bind(ForeignModule& m) { + { + auto& cls = m.klass("TabletBasicInfo"); + REG_VAR(TabletBasicInfo, table_id); + REG_VAR(TabletBasicInfo, partition_id); + REG_VAR(TabletBasicInfo, tablet_id); + REG_VAR(TabletBasicInfo, num_version); + REG_VAR(TabletBasicInfo, max_version); + REG_VAR(TabletBasicInfo, min_version); + REG_VAR(TabletBasicInfo, num_rowset); + REG_VAR(TabletBasicInfo, num_row); + REG_VAR(TabletBasicInfo, data_size); + REG_VAR(TabletBasicInfo, index_mem); + REG_VAR(TabletBasicInfo, create_time); + REG_VAR(TabletBasicInfo, state); + REG_VAR(TabletBasicInfo, type); + } + { + auto& cls = m.klass("TabletSchema"); + REG_METHOD(TabletSchema, num_columns); + REG_METHOD(TabletSchema, num_key_columns); + REG_METHOD(TabletSchema, keys_type); + REG_METHOD(TabletSchema, mem_usage); + cls.func<&TabletSchema::debug_string>("toString"); + } + { + auto& cls = m.klass("Tablet"); + cls.funcExt("tablet_id"); + cls.funcExt("schema"); + cls.funcExt("path"); + cls.funcExt("data_dir"); + cls.funcExt("keys_type_as_int"); + cls.funcExt("tablet_state_as_int"); + REG_METHOD(Tablet, tablet_footprint); + REG_METHOD(Tablet, num_rows); + REG_METHOD(Tablet, version_count); + REG_METHOD(Tablet, max_version); + REG_METHOD(Tablet, max_continuous_version); + REG_METHOD(Tablet, compaction_score); + REG_METHOD(Tablet, schema_debug_string); + REG_METHOD(Tablet, debug_string); + REG_METHOD(Tablet, support_binlog); + REG_METHOD(Tablet, updates); + } + { + auto& cls = m.klass("EditVersionPB"); + cls.funcExt<&proto_to_json>("toString"); + } + { + auto& cls = m.klass("EditVersionMetaPB"); + REG_METHOD(EditVersionMetaPB, version); + REG_METHOD(EditVersionMetaPB, creation_time); + cls.funcExt<&proto_to_json>("toString"); + } + { + auto& cls = m.klass("TabletUpdatesPB"); + REG_METHOD(TabletUpdatesPB, versions_size); + cls.funcExt<&tablet_updates_pb_version>("versions"); + REG_METHOD(TabletUpdatesPB, apply_version); + REG_METHOD(TabletUpdatesPB, next_rowset_id); + REG_METHOD(TabletUpdatesPB, next_log_id); + cls.funcExt<&proto_to_json>("toString"); + } + { + auto& cls = m.klass("EditVersion"); + cls.funcExt<&get_major>("major"); + cls.funcExt<&get_minor>("minor"); + cls.func<&EditVersion::to_string>("toString"); + } + { + auto& cls = m.klass("CompactionInfo"); + REG_VAR(CompactionInfo, start_version); + REG_VAR(CompactionInfo, inputs); + REG_VAR(CompactionInfo, output); + } + { + auto& cls = m.klass("EditVersionInfo"); + REG_VAR(EditVersionInfo, version); + REG_VAR(EditVersionInfo, creation_time); + REG_VAR(EditVersionInfo, rowsets); + REG_VAR(EditVersionInfo, deltas); + REG_METHOD(EditVersionInfo, get_compaction); + } + { + auto& cls = m.klass("Rowset"); + REG_METHOD(Rowset, rowset_id_str); + REG_METHOD(Rowset, schema); + REG_METHOD(Rowset, start_version); + REG_METHOD(Rowset, end_version); + REG_METHOD(Rowset, creation_time); + REG_METHOD(Rowset, data_disk_size); + REG_METHOD(Rowset, empty); + REG_METHOD(Rowset, num_rows); + REG_METHOD(Rowset, total_row_size); + REG_METHOD(Rowset, txn_id); + REG_METHOD(Rowset, partition_id); + REG_METHOD(Rowset, num_segments); + REG_METHOD(Rowset, num_delete_files); + REG_METHOD(Rowset, rowset_path); + } + { + auto& cls = m.klass("TabletUpdates"); + REG_METHOD(TabletUpdates, get_error_msg); + REG_METHOD(TabletUpdates, num_rows); + REG_METHOD(TabletUpdates, data_size); + REG_METHOD(TabletUpdates, num_rowsets); + REG_METHOD(TabletUpdates, max_version); + REG_METHOD(TabletUpdates, version_count); + REG_METHOD(TabletUpdates, num_pending); + REG_METHOD(TabletUpdates, get_compaction_score); + REG_METHOD(TabletUpdates, version_history_count); + REG_METHOD(TabletUpdates, get_average_row_size); + REG_METHOD(TabletUpdates, debug_string); + REG_METHOD(TabletUpdates, get_compaction_score); + REG_METHOD(TabletUpdates, get_version_list); + REG_METHOD(TabletUpdates, get_edit_version); + REG_METHOD(TabletUpdates, get_rowset_map); + cls.funcExt<&tablet_updates_to_pb>("toPB"); + } + { + auto& cls = m.klass("DataDir"); + REG_METHOD(DataDir, path); + REG_METHOD(DataDir, path_hash); + REG_METHOD(DataDir, is_used); + REG_METHOD(DataDir, get_meta); + REG_METHOD(DataDir, is_used); + REG_METHOD(DataDir, available_bytes); + REG_METHOD(DataDir, disk_capacity_bytes); + } + { + auto& cls = m.klass("KVStore"); + REG_METHOD(KVStore, compact); + REG_METHOD(KVStore, flushMemTable); + REG_METHOD(KVStore, get_stats); + cls.funcExt<&kv_store_get_live_sst_files_size>("sst_file_size"); + } + { + auto& cls = m.klass("StorageEngine"); + REG_STATIC_METHOD(StorageEngineRef, get_tablet_info); + REG_STATIC_METHOD(StorageEngineRef, get_tablet_infos); + REG_STATIC_METHOD(StorageEngineRef, get_tablet); + REG_STATIC_METHOD(StorageEngineRef, drop_tablet); + REG_STATIC_METHOD(StorageEngineRef, get_data_dirs); + } + } +}; + +Status execute_script(const std::string& script, std::string& output) { + wrenbind17::VM vm; + vm.setPrintFunc([&](const char* text) { output.append(text); }); + auto& m = vm.module("starrocks"); + bind_common(m); + StorageEngineRef::bind(m); + vm.runFromSource("main", R"(import "starrocks" for StorageEngine)"); + try { + vm.runFromSource("main", script); + } catch (const std::exception& e) { + output.append(e.what()); + } + return Status::OK(); +} + +} // namespace starrocks \ No newline at end of file diff --git a/be/src/script/script.h b/be/src/script/script.h new file mode 100644 index 00000000000..8d75c80b0aa --- /dev/null +++ b/be/src/script/script.h @@ -0,0 +1,25 @@ +// Copyright 2021-present StarRocks, Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include + +#include + +namespace starrocks { + +Status execute_script(const std::string& script, std::string& output); + +} // namespace starrocks diff --git a/be/src/service/internal_service.cpp b/be/src/service/internal_service.cpp index 5b617dae265..ab62e88dc3c 100644 --- a/be/src/service/internal_service.cpp +++ b/be/src/service/internal_service.cpp @@ -963,7 +963,7 @@ void PInternalServiceImplBase::execute_command(google::protobuf::RpcControlle const ExecuteCommandRequestPB* request, ExecuteCommandResultPB* response, google::protobuf::Closure* done) { ClosureGuard closure_guard(done); - Status st = starrocks::execute_command(request->command(), request->params()); + Status st = starrocks::execute_command(request->command(), request->params(), response->mutable_result()); if (!st.ok()) { LOG(WARNING) << "execute_command failed, errmsg=" << st.to_string(); } diff --git a/be/src/storage/data_dir.h b/be/src/storage/data_dir.h index a624346966a..5a13894be51 100644 --- a/be/src/storage/data_dir.h +++ b/be/src/storage/data_dir.h @@ -87,6 +87,9 @@ public: return info; } + int64_t available_bytes() const { return _available_bytes; } + int64_t disk_capacity_bytes() const { return _disk_capacity_bytes; } + // save a cluster_id file under data path to prevent // invalid be config for example two be use the same // data path diff --git a/be/src/storage/kv_store.cpp b/be/src/storage/kv_store.cpp index 32a880eb1fa..aa4cfa3c4a3 100644 --- a/be/src/storage/kv_store.cpp +++ b/be/src/storage/kv_store.cpp @@ -286,10 +286,14 @@ Status KVStore::compact() { return to_status(st); } -Status KVStore::flush() { +Status KVStore::flushWAL() { return to_status(_db->FlushWAL(true)); } +Status KVStore::flushMemTable() { + return to_status(_db->Flush(rocksdb::FlushOptions())); +} + std::string KVStore::get_stats() { rocksdb::ColumnFamilyHandle* handle = _handles[META_COLUMN_FAMILY_INDEX]; std::string stats; diff --git a/be/src/storage/kv_store.h b/be/src/storage/kv_store.h index 47794ed8c33..659749c4433 100644 --- a/be/src/storage/kv_store.h +++ b/be/src/storage/kv_store.h @@ -77,9 +77,13 @@ public: const std::string& upper_bound, std::function const& func); + const std::string& root_path() const { return _root_path; } + Status compact(); - Status flush(); + Status flushWAL(); + + Status flushMemTable(); std::string get_stats(); diff --git a/be/src/storage/rowset/rowset.h b/be/src/storage/rowset/rowset.h index dca99c274ee..194ac8673da 100644 --- a/be/src/storage/rowset/rowset.h +++ b/be/src/storage/rowset/rowset.h @@ -209,6 +209,7 @@ public: size_t total_row_size() const { return rowset_meta()->total_row_size(); } Version version() const { return rowset_meta()->version(); } RowsetId rowset_id() const { return rowset_meta()->rowset_id(); } + std::string rowset_id_str() const { return rowset_meta()->rowset_id().to_string(); } int64_t creation_time() const { return rowset_meta()->creation_time(); } PUniqueId load_id() const { return rowset_meta()->load_id(); } int64_t txn_id() const { return rowset_meta()->txn_id(); } diff --git a/be/src/storage/rowset/rowset_meta_manager.cpp b/be/src/storage/rowset/rowset_meta_manager.cpp index 6c9499e83aa..67b23c47bb9 100644 --- a/be/src/storage/rowset/rowset_meta_manager.cpp +++ b/be/src/storage/rowset/rowset_meta_manager.cpp @@ -67,7 +67,7 @@ Status RowsetMetaManager::save(KVStore* meta, const TabletUid& tablet_uid, const } Status RowsetMetaManager::flush(KVStore* meta) { - return meta->flush(); + return meta->flushWAL(); } Status RowsetMetaManager::remove(KVStore* meta, const TabletUid& tablet_uid, const RowsetId& rowset_id) { diff --git a/be/src/storage/storage_engine.cpp b/be/src/storage/storage_engine.cpp index 6c4eaf14618..835a7b595da 100644 --- a/be/src/storage/storage_engine.cpp +++ b/be/src/storage/storage_engine.cpp @@ -426,6 +426,17 @@ Status StorageEngine::set_cluster_id(int32_t cluster_id) { return Status::OK(); } +std::vector StorageEngine::get_store_paths() { + std::vector paths; + { + std::lock_guard l(_store_lock); + for (auto& it : _store_map) { + paths.push_back(it.first); + } + } + return paths; +} + std::vector StorageEngine::get_stores_for_create_tablet(TStorageMedium::type storage_medium) { std::vector stores; { diff --git a/be/src/storage/storage_engine.h b/be/src/storage/storage_engine.h index 44ba755dfab..44234a9ea82 100644 --- a/be/src/storage/storage_engine.h +++ b/be/src/storage/storage_engine.h @@ -116,6 +116,7 @@ public: Status get_all_data_dir_info(std::vector* data_dir_infos, bool need_update); + std::vector get_store_paths(); // get root path for creating tablet. The returned vector of root path should be random, // for avoiding that all the tablet would be deployed one disk. std::vector get_stores_for_create_tablet(TStorageMedium::type storage_medium); diff --git a/be/src/storage/tablet.cpp b/be/src/storage/tablet.cpp index 52ea77517c2..df53840192b 100644 --- a/be/src/storage/tablet.cpp +++ b/be/src/storage/tablet.cpp @@ -1396,4 +1396,16 @@ void Tablet::get_basic_info(TabletBasicInfo& info) { } } +std::string Tablet::schema_debug_string() const { + return _tablet_meta->tablet_schema().debug_string(); +} + +std::string Tablet::debug_string() const { + if (_updates) { + return _updates->debug_string(); + } + // TODO: add more debug info + return string(); +} + } // namespace starrocks diff --git a/be/src/storage/tablet.h b/be/src/storage/tablet.h index 6d8653d22cc..46350d4f6b5 100644 --- a/be/src/storage/tablet.h +++ b/be/src/storage/tablet.h @@ -117,6 +117,8 @@ public: size_t num_rows_per_row_block() const; size_t next_unique_id() const; size_t field_index(const string& field_name) const; + std::string schema_debug_string() const; + std::string debug_string() const; // operation in rowsets Status add_rowset(const RowsetSharedPtr& rowset, bool need_persist = true); diff --git a/be/src/storage/tablet_updates.cpp b/be/src/storage/tablet_updates.cpp index 3bdf3756272..9baec83214b 100644 --- a/be/src/storage/tablet_updates.cpp +++ b/be/src/storage/tablet_updates.cpp @@ -3721,4 +3721,28 @@ void TabletUpdates::to_rowset_meta_pb(const std::vector& ro } } +std::vector TabletUpdates::get_version_list() const { + std::lock_guard wl(_lock); + std::vector version_list; + for (auto& edit_version_info : _edit_version_infos) { + version_list.emplace_back(edit_version_info->version.to_string()); + } + return version_list; +} + +std::shared_ptr TabletUpdates::get_edit_version(const string& version) const { + std::lock_guard wl(_lock); + for (auto& edit_version_info : _edit_version_infos) { + if (edit_version_info->version.to_string() == version) { + return std::make_shared(*edit_version_info); + } + } + return nullptr; +} + +std::shared_ptr> TabletUpdates::get_rowset_map() const { + std::lock_guard lg(_rowsets_lock); + return std::make_shared>(_rowsets); +} + } // namespace starrocks diff --git a/be/src/storage/tablet_updates.h b/be/src/storage/tablet_updates.h index 42650e4046d..7c63983a5a7 100644 --- a/be/src/storage/tablet_updates.h +++ b/be/src/storage/tablet_updates.h @@ -55,6 +55,30 @@ struct CompactionInfo { uint32_t output = UINT32_MAX; }; +struct EditVersionInfo { + EditVersion version; + int64_t creation_time; + std::vector rowsets; + // used for rowset commit + std::vector deltas; + // used for compaction commit + std::unique_ptr compaction; + EditVersionInfo() = default; + // add a copy constructor to better expose to scripting engine + EditVersionInfo(const EditVersionInfo& rhs) { + version = rhs.version; + creation_time = rhs.creation_time; + rowsets = rhs.rowsets; + deltas = rhs.deltas; + if (rhs.compaction) { + compaction = std::make_unique(); + *compaction = *rhs.compaction; + } + } + // add method to better expose to scripting engine + CompactionInfo* get_compaction() { return compaction.get(); } +}; + // maintain all states for updatable tablets class TabletUpdates { public: @@ -260,6 +284,13 @@ public: void get_basic_info_extra(TabletBasicInfo& info); + // methods used by scripting engine + std::vector get_version_list() const; + + std::shared_ptr get_edit_version(const string& version) const; + + std::shared_ptr> get_rowset_map() const; + private: friend class Tablet; friend class PrimaryIndex; @@ -269,16 +300,6 @@ private: template using OrderedMap = std::map; - struct EditVersionInfo { - EditVersion version; - int64_t creation_time; - std::vector rowsets; - // used for rowset commit - std::vector deltas; - // used for compaction commit - std::unique_ptr compaction; - }; - struct RowsetStats { size_t num_segments = 0; size_t num_rows = 0; diff --git a/be/src/storage/txn_manager.cpp b/be/src/storage/txn_manager.cpp index ba2455cd564..cb6c3d6a430 100644 --- a/be/src/storage/txn_manager.cpp +++ b/be/src/storage/txn_manager.cpp @@ -368,7 +368,7 @@ Status TxnManager::persist_tablet_related_txns(const std::vectordata_dir(); - token->submit_func([&pair_vec, dir, i]() { pair_vec[i].first = dir->get_meta()->flush(); }); + token->submit_func([&pair_vec, dir, i]() { pair_vec[i].first = dir->get_meta()->flushWAL(); }); pair_vec[i].second = tablet->tablet_id(); i++; } @@ -395,7 +395,7 @@ void TxnManager::flush_dirs(std::unordered_set& affected_dirs) { std::vector> pair_vec(affected_dirs.size()); auto token = _flush_thread_pool->new_token(ThreadPool::ExecutionMode::CONCURRENT); for (auto dir : affected_dirs) { - token->submit_func([&pair_vec, dir, i]() { pair_vec[i].first = dir->get_meta()->flush(); }); + token->submit_func([&pair_vec, dir, i]() { pair_vec[i].first = dir->get_meta()->flushWAL(); }); pair_vec[i].second = dir->path(); i++; } diff --git a/be/src/thirdparty/wren/wren.c b/be/src/thirdparty/wren/wren.c new file mode 100644 index 00000000000..21957c384b4 --- /dev/null +++ b/be/src/thirdparty/wren/wren.c @@ -0,0 +1,12266 @@ +// MIT License +// +// Copyright (c) 2013-2021 Robert Nystrom and Wren Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +// Begin file "wren.h" +#ifndef wren_h +#define wren_h + +#include +#include +#include + +// The Wren semantic version number components. +#define WREN_VERSION_MAJOR 0 +#define WREN_VERSION_MINOR 4 +#define WREN_VERSION_PATCH 0 + +// A human-friendly string representation of the version. +#define WREN_VERSION_STRING "0.4.0" + +// A monotonically increasing numeric representation of the version number. Use +// this if you want to do range checks over versions. +#define WREN_VERSION_NUMBER (WREN_VERSION_MAJOR * 1000000 + WREN_VERSION_MINOR * 1000 + WREN_VERSION_PATCH) + +#ifndef WREN_API +#if defined(_MSC_VER) && defined(WREN_API_DLLEXPORT) +#define WREN_API __declspec(dllexport) +#else +#define WREN_API +#endif +#endif //WREN_API + +// A single virtual machine for executing Wren code. +// +// Wren has no global state, so all state stored by a running interpreter lives +// here. +typedef struct WrenVM WrenVM; + +// A handle to a Wren object. +// +// This lets code outside of the VM hold a persistent reference to an object. +// After a handle is acquired, and until it is released, this ensures the +// garbage collector will not reclaim the object it references. +typedef struct WrenHandle WrenHandle; + +// A generic allocation function that handles all explicit memory management +// used by Wren. It's used like so: +// +// - To allocate new memory, [memory] is NULL and [newSize] is the desired +// size. It should return the allocated memory or NULL on failure. +// +// - To attempt to grow an existing allocation, [memory] is the memory, and +// [newSize] is the desired size. It should return [memory] if it was able to +// grow it in place, or a new pointer if it had to move it. +// +// - To shrink memory, [memory] and [newSize] are the same as above but it will +// always return [memory]. +// +// - To free memory, [memory] will be the memory to free and [newSize] will be +// zero. It should return NULL. +typedef void* (*WrenReallocateFn)(void* memory, size_t newSize, void* userData); + +// A function callable from Wren code, but implemented in C. +typedef void (*WrenForeignMethodFn)(WrenVM* vm); + +// A finalizer function for freeing resources owned by an instance of a foreign +// class. Unlike most foreign methods, finalizers do not have access to the VM +// and should not interact with it since it's in the middle of a garbage +// collection. +typedef void (*WrenFinalizerFn)(void* data); + +// Gives the host a chance to canonicalize the imported module name, +// potentially taking into account the (previously resolved) name of the module +// that contains the import. Typically, this is used to implement relative +// imports. +typedef const char* (*WrenResolveModuleFn)(WrenVM* vm, const char* importer, const char* name); + +// Forward declare +struct WrenLoadModuleResult; + +// Called after loadModuleFn is called for module [name]. The original returned result +// is handed back to you in this callback, so that you can free memory if appropriate. +typedef void (*WrenLoadModuleCompleteFn)(WrenVM* vm, const char* name, struct WrenLoadModuleResult result); + +// The result of a loadModuleFn call. +// [source] is the source code for the module, or NULL if the module is not found. +// [onComplete] an optional callback that will be called once Wren is done with the result. +typedef struct WrenLoadModuleResult { + const char* source; + WrenLoadModuleCompleteFn onComplete; + void* userData; +} WrenLoadModuleResult; + +// Loads and returns the source code for the module [name]. +typedef WrenLoadModuleResult (*WrenLoadModuleFn)(WrenVM* vm, const char* name); + +// Returns a pointer to a foreign method on [className] in [module] with +// [signature]. +typedef WrenForeignMethodFn (*WrenBindForeignMethodFn)(WrenVM* vm, const char* module, const char* className, + bool isStatic, const char* signature); + +// Displays a string of text to the user. +typedef void (*WrenWriteFn)(WrenVM* vm, const char* text); + +typedef enum { + // A syntax or resolution error detected at compile time. + WREN_ERROR_COMPILE, + + // The error message for a runtime error. + WREN_ERROR_RUNTIME, + + // One entry of a runtime error's stack trace. + WREN_ERROR_STACK_TRACE +} WrenErrorType; + +// Reports an error to the user. +// +// An error detected during compile time is reported by calling this once with +// [type] `WREN_ERROR_COMPILE`, the resolved name of the [module] and [line] +// where the error occurs, and the compiler's error [message]. +// +// A runtime error is reported by calling this once with [type] +// `WREN_ERROR_RUNTIME`, no [module] or [line], and the runtime error's +// [message]. After that, a series of [type] `WREN_ERROR_STACK_TRACE` calls are +// made for each line in the stack trace. Each of those has the resolved +// [module] and [line] where the method or function is defined and [message] is +// the name of the method or function. +typedef void (*WrenErrorFn)(WrenVM* vm, WrenErrorType type, const char* module, int line, const char* message); + +typedef struct { + // The callback invoked when the foreign object is created. + // + // This must be provided. Inside the body of this, it must call + // [wrenSetSlotNewForeign()] exactly once. + WrenForeignMethodFn allocate; + + // The callback invoked when the garbage collector is about to collect a + // foreign object's memory. + // + // This may be `NULL` if the foreign class does not need to finalize. + WrenFinalizerFn finalize; +} WrenForeignClassMethods; + +// Returns a pair of pointers to the foreign methods used to allocate and +// finalize the data for instances of [className] in resolved [module]. +typedef WrenForeignClassMethods (*WrenBindForeignClassFn)(WrenVM* vm, const char* module, const char* className); + +typedef struct { + // The callback Wren will use to allocate, reallocate, and deallocate memory. + // + // If `NULL`, defaults to a built-in function that uses `realloc` and `free`. + WrenReallocateFn reallocateFn; + + // The callback Wren uses to resolve a module name. + // + // Some host applications may wish to support "relative" imports, where the + // meaning of an import string depends on the module that contains it. To + // support that without baking any policy into Wren itself, the VM gives the + // host a chance to resolve an import string. + // + // Before an import is loaded, it calls this, passing in the name of the + // module that contains the import and the import string. The host app can + // look at both of those and produce a new "canonical" string that uniquely + // identifies the module. This string is then used as the name of the module + // going forward. It is what is passed to [loadModuleFn], how duplicate + // imports of the same module are detected, and how the module is reported in + // stack traces. + // + // If you leave this function NULL, then the original import string is + // treated as the resolved string. + // + // If an import cannot be resolved by the embedder, it should return NULL and + // Wren will report that as a runtime error. + // + // Wren will take ownership of the string you return and free it for you, so + // it should be allocated using the same allocation function you provide + // above. + WrenResolveModuleFn resolveModuleFn; + + // The callback Wren uses to load a module. + // + // Since Wren does not talk directly to the file system, it relies on the + // embedder to physically locate and read the source code for a module. The + // first time an import appears, Wren will call this and pass in the name of + // the module being imported. The method will return a result, which contains + // the source code for that module. Memory for the source is owned by the + // host application, and can be freed using the onComplete callback. + // + // This will only be called once for any given module name. Wren caches the + // result internally so subsequent imports of the same module will use the + // previous source and not call this. + // + // If a module with the given name could not be found by the embedder, it + // should return NULL and Wren will report that as a runtime error. + WrenLoadModuleFn loadModuleFn; + + // The callback Wren uses to find a foreign method and bind it to a class. + // + // When a foreign method is declared in a class, this will be called with the + // foreign method's module, class, and signature when the class body is + // executed. It should return a pointer to the foreign function that will be + // bound to that method. + // + // If the foreign function could not be found, this should return NULL and + // Wren will report it as runtime error. + WrenBindForeignMethodFn bindForeignMethodFn; + + // The callback Wren uses to find a foreign class and get its foreign methods. + // + // When a foreign class is declared, this will be called with the class's + // module and name when the class body is executed. It should return the + // foreign functions uses to allocate and (optionally) finalize the bytes + // stored in the foreign object when an instance is created. + WrenBindForeignClassFn bindForeignClassFn; + + // The callback Wren uses to display text when `System.print()` or the other + // related functions are called. + // + // If this is `NULL`, Wren discards any printed text. + WrenWriteFn writeFn; + + // The callback Wren uses to report errors. + // + // When an error occurs, this will be called with the module name, line + // number, and an error message. If this is `NULL`, Wren doesn't report any + // errors. + WrenErrorFn errorFn; + + // The number of bytes Wren will allocate before triggering the first garbage + // collection. + // + // If zero, defaults to 10MB. + size_t initialHeapSize; + + // After a collection occurs, the threshold for the next collection is + // determined based on the number of bytes remaining in use. This allows Wren + // to shrink its memory usage automatically after reclaiming a large amount + // of memory. + // + // This can be used to ensure that the heap does not get too small, which can + // in turn lead to a large number of collections afterwards as the heap grows + // back to a usable size. + // + // If zero, defaults to 1MB. + size_t minHeapSize; + + // Wren will resize the heap automatically as the number of bytes + // remaining in use after a collection changes. This number determines the + // amount of additional memory Wren will use after a collection, as a + // percentage of the current heap size. + // + // For example, say that this is 50. After a garbage collection, when there + // are 400 bytes of memory still in use, the next collection will be triggered + // after a total of 600 bytes are allocated (including the 400 already in + // use.) + // + // Setting this to a smaller number wastes less memory, but triggers more + // frequent garbage collections. + // + // If zero, defaults to 50. + int heapGrowthPercent; + + // User-defined data associated with the VM. + void* userData; + +} WrenConfiguration; + +typedef enum { WREN_RESULT_SUCCESS, WREN_RESULT_COMPILE_ERROR, WREN_RESULT_RUNTIME_ERROR } WrenInterpretResult; + +// The type of an object stored in a slot. +// +// This is not necessarily the object's *class*, but instead its low level +// representation type. +typedef enum { + WREN_TYPE_BOOL, + WREN_TYPE_NUM, + WREN_TYPE_FOREIGN, + WREN_TYPE_LIST, + WREN_TYPE_MAP, + WREN_TYPE_NULL, + WREN_TYPE_STRING, + + // The object is of a type that isn't accessible by the C API. + WREN_TYPE_UNKNOWN +} WrenType; + +// Get the current wren version number. +// +// Can be used to range checks over versions. +WREN_API int wrenGetVersionNumber(); + +// Initializes [configuration] with all of its default values. +// +// Call this before setting the particular fields you care about. +WREN_API void wrenInitConfiguration(WrenConfiguration* configuration); + +// Creates a new Wren virtual machine using the given [configuration]. Wren +// will copy the configuration data, so the argument passed to this can be +// freed after calling this. If [configuration] is `NULL`, uses a default +// configuration. +WREN_API WrenVM* wrenNewVM(WrenConfiguration* configuration); + +// Disposes of all resources is use by [vm], which was previously created by a +// call to [wrenNewVM]. +WREN_API void wrenFreeVM(WrenVM* vm); + +// Immediately run the garbage collector to free unused memory. +WREN_API void wrenCollectGarbage(WrenVM* vm); + +// Runs [source], a string of Wren source code in a new fiber in [vm] in the +// context of resolved [module]. +WREN_API WrenInterpretResult wrenInterpret(WrenVM* vm, const char* module, const char* source); + +// Creates a handle that can be used to invoke a method with [signature] on +// using a receiver and arguments that are set up on the stack. +// +// This handle can be used repeatedly to directly invoke that method from C +// code using [wrenCall]. +// +// When you are done with this handle, it must be released using +// [wrenReleaseHandle]. +WREN_API WrenHandle* wrenMakeCallHandle(WrenVM* vm, const char* signature); + +// Calls [method], using the receiver and arguments previously set up on the +// stack. +// +// [method] must have been created by a call to [wrenMakeCallHandle]. The +// arguments to the method must be already on the stack. The receiver should be +// in slot 0 with the remaining arguments following it, in order. It is an +// error if the number of arguments provided does not match the method's +// signature. +// +// After this returns, you can access the return value from slot 0 on the stack. +WREN_API WrenInterpretResult wrenCall(WrenVM* vm, WrenHandle* method); + +// Releases the reference stored in [handle]. After calling this, [handle] can +// no longer be used. +WREN_API void wrenReleaseHandle(WrenVM* vm, WrenHandle* handle); + +// The following functions are intended to be called from foreign methods or +// finalizers. The interface Wren provides to a foreign method is like a +// register machine: you are given a numbered array of slots that values can be +// read from and written to. Values always live in a slot (unless explicitly +// captured using wrenGetSlotHandle(), which ensures the garbage collector can +// find them. +// +// When your foreign function is called, you are given one slot for the receiver +// and each argument to the method. The receiver is in slot 0 and the arguments +// are in increasingly numbered slots after that. You are free to read and +// write to those slots as you want. If you want more slots to use as scratch +// space, you can call wrenEnsureSlots() to add more. +// +// When your function returns, every slot except slot zero is discarded and the +// value in slot zero is used as the return value of the method. If you don't +// store a return value in that slot yourself, it will retain its previous +// value, the receiver. +// +// While Wren is dynamically typed, C is not. This means the C interface has to +// support the various types of primitive values a Wren variable can hold: bool, +// double, string, etc. If we supported this for every operation in the C API, +// there would be a combinatorial explosion of functions, like "get a +// double-valued element from a list", "insert a string key and double value +// into a map", etc. +// +// To avoid that, the only way to convert to and from a raw C value is by going +// into and out of a slot. All other functions work with values already in a +// slot. So, to add an element to a list, you put the list in one slot, and the +// element in another. Then there is a single API function wrenInsertInList() +// that takes the element out of that slot and puts it into the list. +// +// The goal of this API is to be easy to use while not compromising performance. +// The latter means it does not do type or bounds checking at runtime except +// using assertions which are generally removed from release builds. C is an +// unsafe language, so it's up to you to be careful to use it correctly. In +// return, you get a very fast FFI. + +// Returns the number of slots available to the current foreign method. +WREN_API int wrenGetSlotCount(WrenVM* vm); + +// Ensures that the foreign method stack has at least [numSlots] available for +// use, growing the stack if needed. +// +// Does not shrink the stack if it has more than enough slots. +// +// It is an error to call this from a finalizer. +WREN_API void wrenEnsureSlots(WrenVM* vm, int numSlots); + +// Gets the type of the object in [slot]. +WREN_API WrenType wrenGetSlotType(WrenVM* vm, int slot); + +// Reads a boolean value from [slot]. +// +// It is an error to call this if the slot does not contain a boolean value. +WREN_API bool wrenGetSlotBool(WrenVM* vm, int slot); + +// Reads a byte array from [slot]. +// +// The memory for the returned string is owned by Wren. You can inspect it +// while in your foreign method, but cannot keep a pointer to it after the +// function returns, since the garbage collector may reclaim it. +// +// Returns a pointer to the first byte of the array and fill [length] with the +// number of bytes in the array. +// +// It is an error to call this if the slot does not contain a string. +WREN_API const char* wrenGetSlotBytes(WrenVM* vm, int slot, int* length); + +// Reads a number from [slot]. +// +// It is an error to call this if the slot does not contain a number. +WREN_API double wrenGetSlotDouble(WrenVM* vm, int slot); + +// Reads a foreign object from [slot] and returns a pointer to the foreign data +// stored with it. +// +// It is an error to call this if the slot does not contain an instance of a +// foreign class. +WREN_API void* wrenGetSlotForeign(WrenVM* vm, int slot); + +// Reads a string from [slot]. +// +// The memory for the returned string is owned by Wren. You can inspect it +// while in your foreign method, but cannot keep a pointer to it after the +// function returns, since the garbage collector may reclaim it. +// +// It is an error to call this if the slot does not contain a string. +WREN_API const char* wrenGetSlotString(WrenVM* vm, int slot); + +// Creates a handle for the value stored in [slot]. +// +// This will prevent the object that is referred to from being garbage collected +// until the handle is released by calling [wrenReleaseHandle()]. +WREN_API WrenHandle* wrenGetSlotHandle(WrenVM* vm, int slot); + +// Stores the boolean [value] in [slot]. +WREN_API void wrenSetSlotBool(WrenVM* vm, int slot, bool value); + +// Stores the array [length] of [bytes] in [slot]. +// +// The bytes are copied to a new string within Wren's heap, so you can free +// memory used by them after this is called. +WREN_API void wrenSetSlotBytes(WrenVM* vm, int slot, const char* bytes, size_t length); + +// Stores the numeric [value] in [slot]. +WREN_API void wrenSetSlotDouble(WrenVM* vm, int slot, double value); + +// Creates a new instance of the foreign class stored in [classSlot] with [size] +// bytes of raw storage and places the resulting object in [slot]. +// +// This does not invoke the foreign class's constructor on the new instance. If +// you need that to happen, call the constructor from Wren, which will then +// call the allocator foreign method. In there, call this to create the object +// and then the constructor will be invoked when the allocator returns. +// +// Returns a pointer to the foreign object's data. +WREN_API void* wrenSetSlotNewForeign(WrenVM* vm, int slot, int classSlot, size_t size); + +// Stores a new empty list in [slot]. +WREN_API void wrenSetSlotNewList(WrenVM* vm, int slot); + +// Stores a new empty map in [slot]. +WREN_API void wrenSetSlotNewMap(WrenVM* vm, int slot); + +// Stores null in [slot]. +WREN_API void wrenSetSlotNull(WrenVM* vm, int slot); + +// Stores the string [text] in [slot]. +// +// The [text] is copied to a new string within Wren's heap, so you can free +// memory used by it after this is called. The length is calculated using +// [strlen()]. If the string may contain any null bytes in the middle, then you +// should use [wrenSetSlotBytes()] instead. +WREN_API void wrenSetSlotString(WrenVM* vm, int slot, const char* text); + +// Stores the value captured in [handle] in [slot]. +// +// This does not release the handle for the value. +WREN_API void wrenSetSlotHandle(WrenVM* vm, int slot, WrenHandle* handle); + +// Returns the number of elements in the list stored in [slot]. +WREN_API int wrenGetListCount(WrenVM* vm, int slot); + +// Reads element [index] from the list in [listSlot] and stores it in +// [elementSlot]. +WREN_API void wrenGetListElement(WrenVM* vm, int listSlot, int index, int elementSlot); + +// Sets the value stored at [index] in the list at [listSlot], +// to the value from [elementSlot]. +WREN_API void wrenSetListElement(WrenVM* vm, int listSlot, int index, int elementSlot); + +// Takes the value stored at [elementSlot] and inserts it into the list stored +// at [listSlot] at [index]. +// +// As in Wren, negative indexes can be used to insert from the end. To append +// an element, use `-1` for the index. +WREN_API void wrenInsertInList(WrenVM* vm, int listSlot, int index, int elementSlot); + +// Returns the number of entries in the map stored in [slot]. +WREN_API int wrenGetMapCount(WrenVM* vm, int slot); + +// Returns true if the key in [keySlot] is found in the map placed in [mapSlot]. +WREN_API bool wrenGetMapContainsKey(WrenVM* vm, int mapSlot, int keySlot); + +// Retrieves a value with the key in [keySlot] from the map in [mapSlot] and +// stores it in [valueSlot]. +WREN_API void wrenGetMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot); + +// Takes the value stored at [valueSlot] and inserts it into the map stored +// at [mapSlot] with key [keySlot]. +WREN_API void wrenSetMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot); + +// Removes a value from the map in [mapSlot], with the key from [keySlot], +// and place it in [removedValueSlot]. If not found, [removedValueSlot] is +// set to null, the same behaviour as the Wren Map API. +WREN_API void wrenRemoveMapValue(WrenVM* vm, int mapSlot, int keySlot, int removedValueSlot); + +// Looks up the top level variable with [name] in resolved [module] and stores +// it in [slot]. +WREN_API void wrenGetVariable(WrenVM* vm, const char* module, const char* name, int slot); + +// Looks up the top level variable with [name] in resolved [module], +// returns false if not found. The module must be imported at the time, +// use wrenHasModule to ensure that before calling. +WREN_API bool wrenHasVariable(WrenVM* vm, const char* module, const char* name); + +// Returns true if [module] has been imported/resolved before, false if not. +WREN_API bool wrenHasModule(WrenVM* vm, const char* module); + +// Sets the current fiber to be aborted, and uses the value in [slot] as the +// runtime error object. +WREN_API void wrenAbortFiber(WrenVM* vm, int slot); + +// Returns the user data associated with the WrenVM. +WREN_API void* wrenGetUserData(WrenVM* vm); + +// Sets user data associated with the WrenVM. +WREN_API void wrenSetUserData(WrenVM* vm, void* userData); + +#endif +// End file "wren.h" +// Begin file "wren_debug.h" +#ifndef wren_debug_h +#define wren_debug_h + +// Begin file "wren_value.h" +#ifndef wren_value_h +#define wren_value_h + +#include +#include + +// Begin file "wren_common.h" +#ifndef wren_common_h +#define wren_common_h + +// This header contains macros and defines used across the entire Wren +// implementation. In particular, it contains "configuration" defines that +// control how Wren works. Some of these are only used while hacking on Wren +// itself. +// +// This header is *not* intended to be included by code outside of Wren itself. + +// Wren pervasively uses the C99 integer types (uint16_t, etc.) along with some +// of the associated limit constants (UINT32_MAX, etc.). The constants are not +// part of standard C++, so aren't included by default by C++ compilers when you +// include unless __STDC_LIMIT_MACROS is defined. +#define __STDC_LIMIT_MACROS +#include + +// These flags let you control some details of the interpreter's implementation. +// Usually they trade-off a bit of portability for speed. They default to the +// most efficient behavior. + +// If true, then Wren uses a NaN-tagged double for its core value +// representation. Otherwise, it uses a larger more conventional struct. The +// former is significantly faster and more compact. The latter is useful for +// debugging and may be more portable. +// +// Defaults to on. +#ifndef WREN_NAN_TAGGING +#define WREN_NAN_TAGGING 1 +#endif + +// If true, the VM's interpreter loop uses computed gotos. See this for more: +// http://gcc.gnu.org/onlinedocs/gcc-3.1.1/gcc/Labels-as-Values.html +// Enabling this speeds up the main dispatch loop a bit, but requires compiler +// support. +// see https://bullno1.com/blog/switched-goto for alternative +// Defaults to true on supported compilers. +#ifndef WREN_COMPUTED_GOTO +#if defined(_MSC_VER) && !defined(__clang__) +// No computed gotos in Visual Studio. +#define WREN_COMPUTED_GOTO 0 +#else +#define WREN_COMPUTED_GOTO 1 +#endif +#endif + +// The VM includes a number of optional modules. You can choose to include +// these or not. By default, they are all available. To disable one, set the +// corresponding `WREN_OPT_` define to `0`. +#ifndef WREN_OPT_META +#define WREN_OPT_META 1 +#endif + +#ifndef WREN_OPT_RANDOM +#define WREN_OPT_RANDOM 1 +#endif + +// These flags are useful for debugging and hacking on Wren itself. They are not +// intended to be used for production code. They default to off. + +// Set this to true to stress test the GC. It will perform a collection before +// every allocation. This is useful to ensure that memory is always correctly +// reachable. +#define WREN_DEBUG_GC_STRESS 0 + +// Set this to true to log memory operations as they occur. +#define WREN_DEBUG_TRACE_MEMORY 0 + +// Set this to true to log garbage collections as they occur. +#define WREN_DEBUG_TRACE_GC 0 + +// Set this to true to print out the compiled bytecode of each function. +#define WREN_DEBUG_DUMP_COMPILED_CODE 0 + +// Set this to trace each instruction as it's executed. +#define WREN_DEBUG_TRACE_INSTRUCTIONS 0 + +// The maximum number of module-level variables that may be defined at one time. +// This limitation comes from the 16 bits used for the arguments to +// `CODE_LOAD_MODULE_VAR` and `CODE_STORE_MODULE_VAR`. +#define MAX_MODULE_VARS 65536 + +// The maximum number of arguments that can be passed to a method. Note that +// this limitation is hardcoded in other places in the VM, in particular, the +// `CODE_CALL_XX` instructions assume a certain maximum number. +#define MAX_PARAMETERS 16 + +// The maximum name of a method, not including the signature. This is an +// arbitrary but enforced maximum just so we know how long the method name +// strings need to be in the parser. +#define MAX_METHOD_NAME 64 + +// The maximum length of a method signature. Signatures look like: +// +// foo // Getter. +// foo() // No-argument method. +// foo(_) // One-argument method. +// foo(_,_) // Two-argument method. +// init foo() // Constructor initializer. +// +// The maximum signature length takes into account the longest method name, the +// maximum number of parameters with separators between them, "init ", and "()". +#define MAX_METHOD_SIGNATURE (MAX_METHOD_NAME + (MAX_PARAMETERS * 2) + 6) + +// The maximum length of an identifier. The only real reason for this limitation +// is so that error messages mentioning variables can be stack allocated. +#define MAX_VARIABLE_NAME 64 + +// The maximum number of fields a class can have, including inherited fields. +// This is explicit in the bytecode since `CODE_CLASS` and `CODE_SUBCLASS` take +// a single byte for the number of fields. Note that it's 255 and not 256 +// because creating a class takes the *number* of fields, not the *highest +// field index*. +#define MAX_FIELDS 255 + +// Use the VM's allocator to allocate an object of [type]. +#define ALLOCATE(vm, type) ((type*)wrenReallocate(vm, NULL, 0, sizeof(type))) + +// Use the VM's allocator to allocate an object of [mainType] containing a +// flexible array of [count] objects of [arrayType]. +#define ALLOCATE_FLEX(vm, mainType, arrayType, count) \ + ((mainType*)wrenReallocate(vm, NULL, 0, sizeof(mainType) + sizeof(arrayType) * (count))) + +// Use the VM's allocator to allocate an array of [count] elements of [type]. +#define ALLOCATE_ARRAY(vm, type, count) ((type*)wrenReallocate(vm, NULL, 0, sizeof(type) * (count))) + +// Use the VM's allocator to free the previously allocated memory at [pointer]. +#define DEALLOCATE(vm, pointer) wrenReallocate(vm, pointer, 0, 0) + +// The Microsoft compiler does not support the "inline" modifier when compiling +// as plain C. +#if defined(_MSC_VER) && !defined(__cplusplus) +#define inline _inline +#endif + +// This is used to clearly mark flexible-sized arrays that appear at the end of +// some dynamically-allocated structs, known as the "struct hack". +#if __STDC_VERSION__ >= 199901L +// In C99, a flexible array member is just "[]". +#define FLEXIBLE_ARRAY +#else +// Elsewhere, use a zero-sized array. It's technically undefined behavior, +// but works reliably in most known compilers. +#define FLEXIBLE_ARRAY 0 +#endif + +// Assertions are used to validate program invariants. They indicate things the +// program expects to be true about its internal state during execution. If an +// assertion fails, there is a bug in Wren. +// +// Assertions add significant overhead, so are only enabled in debug builds. +#ifdef DEBUG + +#include + +#define ASSERT(condition, message) \ + do { \ + if (!(condition)) { \ + fprintf(stderr, "[%s:%d] Assert failed in %s(): %s\n", __FILE__, __LINE__, __func__, message); \ + abort(); \ + } \ + } while (false) + +// Indicates that we know execution should never reach this point in the +// program. In debug mode, we assert this fact because it's a bug to get here. +// +// In release mode, we use compiler-specific built in functions to tell the +// compiler the code can't be reached. This avoids "missing return" warnings +// in some cases and also lets it perform some optimizations by assuming the +// code is never reached. +#define UNREACHABLE() \ + do { \ + fprintf(stderr, "[%s:%d] This code should not be reached in %s()\n", __FILE__, __LINE__, __func__); \ + abort(); \ + } while (false) + +#else + +#define ASSERT(condition, message) \ + do { \ + } while (false) + +// Tell the compiler that this part of the code will never be reached. +#if defined(_MSC_VER) +#define UNREACHABLE() __assume(0) +#elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)) +#define UNREACHABLE() __builtin_unreachable() +#else +#define UNREACHABLE() +#endif + +#endif + +#endif +// End file "wren_common.h" +// Begin file "wren_math.h" +#ifndef wren_math_h +#define wren_math_h + +#include +#include + +// A union to let us reinterpret a double as raw bits and back. +typedef union { + uint64_t bits64; + uint32_t bits32[2]; + double num; +} WrenDoubleBits; + +#define WREN_DOUBLE_QNAN_POS_MIN_BITS (UINT64_C(0x7FF8000000000000)) +#define WREN_DOUBLE_QNAN_POS_MAX_BITS (UINT64_C(0x7FFFFFFFFFFFFFFF)) + +#define WREN_DOUBLE_NAN (wrenDoubleFromBits(WREN_DOUBLE_QNAN_POS_MIN_BITS)) + +static inline double wrenDoubleFromBits(uint64_t bits) { + WrenDoubleBits data; + data.bits64 = bits; + return data.num; +} + +static inline uint64_t wrenDoubleToBits(double num) { + WrenDoubleBits data; + data.num = num; + return data.bits64; +} + +#endif +// End file "wren_math.h" +// Begin file "wren_utils.h" +#ifndef wren_utils_h +#define wren_utils_h + +// Reusable data structures and other utility functions. + +// Forward declare this here to break a cycle between wren_utils.h and +// wren_value.h. +typedef struct sObjString ObjString; + +// We need buffers of a few different types. To avoid lots of casting between +// void* and back, we'll use the preprocessor as a poor man's generics and let +// it generate a few type-specific ones. +#define DECLARE_BUFFER(name, type) \ + typedef struct { \ + type* data; \ + int count; \ + int capacity; \ + } name##Buffer; \ + void wren##name##BufferInit(name##Buffer* buffer); \ + void wren##name##BufferClear(WrenVM* vm, name##Buffer* buffer); \ + void wren##name##BufferFill(WrenVM* vm, name##Buffer* buffer, type data, int count); \ + void wren##name##BufferWrite(WrenVM* vm, name##Buffer* buffer, type data) + +// This should be used once for each type instantiation, somewhere in a .c file. +#define DEFINE_BUFFER(name, type) \ + void wren##name##BufferInit(name##Buffer* buffer) { \ + buffer->data = NULL; \ + buffer->capacity = 0; \ + buffer->count = 0; \ + } \ + \ + void wren##name##BufferClear(WrenVM* vm, name##Buffer* buffer) { \ + wrenReallocate(vm, buffer->data, 0, 0); \ + wren##name##BufferInit(buffer); \ + } \ + \ + void wren##name##BufferFill(WrenVM* vm, name##Buffer* buffer, type data, int count) { \ + if (buffer->capacity < buffer->count + count) { \ + int capacity = wrenPowerOf2Ceil(buffer->count + count); \ + buffer->data = \ + (type*)wrenReallocate(vm, buffer->data, buffer->capacity * sizeof(type), capacity * sizeof(type)); \ + buffer->capacity = capacity; \ + } \ + \ + for (int i = 0; i < count; i++) { \ + buffer->data[buffer->count++] = data; \ + } \ + } \ + \ + void wren##name##BufferWrite(WrenVM* vm, name##Buffer* buffer, type data) { \ + wren##name##BufferFill(vm, buffer, data, 1); \ + } + +DECLARE_BUFFER(Byte, uint8_t); +DECLARE_BUFFER(Int, int); +DECLARE_BUFFER(String, ObjString*); + +// TODO: Change this to use a map. +typedef StringBuffer SymbolTable; + +// Initializes the symbol table. +void wrenSymbolTableInit(SymbolTable* symbols); + +// Frees all dynamically allocated memory used by the symbol table, but not the +// SymbolTable itself. +void wrenSymbolTableClear(WrenVM* vm, SymbolTable* symbols); + +// Adds name to the symbol table. Returns the index of it in the table. +int wrenSymbolTableAdd(WrenVM* vm, SymbolTable* symbols, const char* name, size_t length); + +// Adds name to the symbol table. Returns the index of it in the table. Will +// use an existing symbol if already present. +int wrenSymbolTableEnsure(WrenVM* vm, SymbolTable* symbols, const char* name, size_t length); + +// Looks up name in the symbol table. Returns its index if found or -1 if not. +int wrenSymbolTableFind(const SymbolTable* symbols, const char* name, size_t length); + +void wrenBlackenSymbolTable(WrenVM* vm, SymbolTable* symbolTable); + +// Returns the number of bytes needed to encode [value] in UTF-8. +// +// Returns 0 if [value] is too large to encode. +int wrenUtf8EncodeNumBytes(int value); + +// Encodes value as a series of bytes in [bytes], which is assumed to be large +// enough to hold the encoded result. +// +// Returns the number of written bytes. +int wrenUtf8Encode(int value, uint8_t* bytes); + +// Decodes the UTF-8 sequence starting at [bytes] (which has max [length]), +// returning the code point. +// +// Returns -1 if the bytes are not a valid UTF-8 sequence. +int wrenUtf8Decode(const uint8_t* bytes, uint32_t length); + +// Returns the number of bytes in the UTF-8 sequence starting with [byte]. +// +// If the character at that index is not the beginning of a UTF-8 sequence, +// returns 0. +int wrenUtf8DecodeNumBytes(uint8_t byte); + +// Returns the smallest power of two that is equal to or greater than [n]. +int wrenPowerOf2Ceil(int n); + +// Validates that [value] is within `[0, count)`. Also allows +// negative indices which map backwards from the end. Returns the valid positive +// index value. If invalid, returns `UINT32_MAX`. +uint32_t wrenValidateIndex(uint32_t count, int64_t value); + +#endif +// End file "wren_utils.h" + +// This defines the built-in types and their core representations in memory. +// Since Wren is dynamically typed, any variable can hold a value of any type, +// and the type can change at runtime. Implementing this efficiently is +// critical for performance. +// +// The main type exposed by this is [Value]. A C variable of that type is a +// storage location that can hold any Wren value. The stack, module variables, +// and instance fields are all implemented in C as variables of type Value. +// +// The built-in types for booleans, numbers, and null are unboxed: their value +// is stored directly in the Value, and copying a Value copies the value. Other +// types--classes, instances of classes, functions, lists, and strings--are all +// reference types. They are stored on the heap and the Value just stores a +// pointer to it. Copying the Value copies a reference to the same object. The +// Wren implementation calls these "Obj", or objects, though to a user, all +// values are objects. +// +// There is also a special singleton value "undefined". It is used internally +// but never appears as a real value to a user. It has two uses: +// +// - It is used to identify module variables that have been implicitly declared +// by use in a forward reference but not yet explicitly declared. These only +// exist during compilation and do not appear at runtime. +// +// - It is used to represent unused map entries in an ObjMap. +// +// There are two supported Value representations. The main one uses a technique +// called "NaN tagging" (explained in detail below) to store a number, any of +// the value types, or a pointer, all inside one double-precision floating +// point number. A larger, slower, Value type that uses a struct to store these +// is also supported, and is useful for debugging the VM. +// +// The representation is controlled by the `WREN_NAN_TAGGING` define. If that's +// defined, Nan tagging is used. + +// These macros cast a Value to one of the specific object types. These do *not* +// perform any validation, so must only be used after the Value has been +// ensured to be the right type. +#define AS_CLASS(value) ((ObjClass*)AS_OBJ(value)) // ObjClass* +#define AS_CLOSURE(value) ((ObjClosure*)AS_OBJ(value)) // ObjClosure* +#define AS_FIBER(v) ((ObjFiber*)AS_OBJ(v)) // ObjFiber* +#define AS_FN(value) ((ObjFn*)AS_OBJ(value)) // ObjFn* +#define AS_FOREIGN(v) ((ObjForeign*)AS_OBJ(v)) // ObjForeign* +#define AS_INSTANCE(value) ((ObjInstance*)AS_OBJ(value)) // ObjInstance* +#define AS_LIST(value) ((ObjList*)AS_OBJ(value)) // ObjList* +#define AS_MAP(value) ((ObjMap*)AS_OBJ(value)) // ObjMap* +#define AS_MODULE(value) ((ObjModule*)AS_OBJ(value)) // ObjModule* +#define AS_NUM(value) (wrenValueToNum(value)) // double +#define AS_RANGE(v) ((ObjRange*)AS_OBJ(v)) // ObjRange* +#define AS_STRING(v) ((ObjString*)AS_OBJ(v)) // ObjString* +#define AS_CSTRING(v) (AS_STRING(v)->value) // const char* + +// These macros promote a primitive C value to a full Wren Value. There are +// more defined below that are specific to the Nan tagged or other +// representation. +#define BOOL_VAL(boolean) ((boolean) ? TRUE_VAL : FALSE_VAL) // boolean +#define NUM_VAL(num) (wrenNumToValue(num)) // double +#define OBJ_VAL(obj) (wrenObjectToValue((Obj*)(obj))) // Any Obj___* + +// These perform type tests on a Value, returning `true` if the Value is of the +// given type. +#define IS_BOOL(value) (wrenIsBool(value)) // Bool +#define IS_CLASS(value) (wrenIsObjType(value, OBJ_CLASS)) // ObjClass +#define IS_CLOSURE(value) (wrenIsObjType(value, OBJ_CLOSURE)) // ObjClosure +#define IS_FIBER(value) (wrenIsObjType(value, OBJ_FIBER)) // ObjFiber +#define IS_FN(value) (wrenIsObjType(value, OBJ_FN)) // ObjFn +#define IS_FOREIGN(value) (wrenIsObjType(value, OBJ_FOREIGN)) // ObjForeign +#define IS_INSTANCE(value) (wrenIsObjType(value, OBJ_INSTANCE)) // ObjInstance +#define IS_LIST(value) (wrenIsObjType(value, OBJ_LIST)) // ObjList +#define IS_MAP(value) (wrenIsObjType(value, OBJ_MAP)) // ObjMap +#define IS_RANGE(value) (wrenIsObjType(value, OBJ_RANGE)) // ObjRange +#define IS_STRING(value) (wrenIsObjType(value, OBJ_STRING)) // ObjString + +// Creates a new string object from [text], which should be a bare C string +// literal. This determines the length of the string automatically at compile +// time based on the size of the character array (-1 for the terminating '\0'). +#define CONST_STRING(vm, text) wrenNewStringLength((vm), (text), sizeof(text) - 1) + +// Identifies which specific type a heap-allocated object is. +typedef enum { + OBJ_CLASS, + OBJ_CLOSURE, + OBJ_FIBER, + OBJ_FN, + OBJ_FOREIGN, + OBJ_INSTANCE, + OBJ_LIST, + OBJ_MAP, + OBJ_MODULE, + OBJ_RANGE, + OBJ_STRING, + OBJ_UPVALUE +} ObjType; + +typedef struct sObjClass ObjClass; + +// Base struct for all heap-allocated objects. +typedef struct sObj Obj; +struct sObj { + ObjType type; + bool isDark; + + // The object's class. + ObjClass* classObj; + + // The next object in the linked list of all currently allocated objects. + struct sObj* next; +}; + +#if WREN_NAN_TAGGING + +typedef uint64_t Value; + +#else + +typedef enum { VAL_FALSE, VAL_NULL, VAL_NUM, VAL_TRUE, VAL_UNDEFINED, VAL_OBJ } ValueType; + +typedef struct { + ValueType type; + union { + double num; + Obj* obj; + } as; +} Value; + +#endif + +DECLARE_BUFFER(Value, Value); + +// A heap-allocated string object. +struct sObjString { + Obj obj; + + // Number of bytes in the string, not including the null terminator. + uint32_t length; + + // The hash value of the string's contents. + uint32_t hash; + + // Inline array of the string's bytes followed by a null terminator. + char value[FLEXIBLE_ARRAY]; +}; + +// The dynamically allocated data structure for a variable that has been used +// by a closure. Whenever a function accesses a variable declared in an +// enclosing function, it will get to it through this. +// +// An upvalue can be either "closed" or "open". An open upvalue points directly +// to a [Value] that is still stored on the fiber's stack because the local +// variable is still in scope in the function where it's declared. +// +// When that local variable goes out of scope, the upvalue pointing to it will +// be closed. When that happens, the value gets copied off the stack into the +// upvalue itself. That way, it can have a longer lifetime than the stack +// variable. +typedef struct sObjUpvalue { + // The object header. Note that upvalues have this because they are garbage + // collected, but they are not first class Wren objects. + Obj obj; + + // Pointer to the variable this upvalue is referencing. + Value* value; + + // If the upvalue is closed (i.e. the local variable it was pointing to has + // been popped off the stack) then the closed-over value will be hoisted out + // of the stack into here. [value] will then be changed to point to this. + Value closed; + + // Open upvalues are stored in a linked list by the fiber. This points to the + // next upvalue in that list. + struct sObjUpvalue* next; +} ObjUpvalue; + +// The type of a primitive function. +// +// Primitives are similar to foreign functions, but have more direct access to +// VM internals. It is passed the arguments in [args]. If it returns a value, +// it places it in `args[0]` and returns `true`. If it causes a runtime error +// or modifies the running fiber, it returns `false`. +typedef bool (*Primitive)(WrenVM* vm, Value* args); + +// TODO: See if it's actually a perf improvement to have this in a separate +// struct instead of in ObjFn. +// Stores debugging information for a function used for things like stack +// traces. +typedef struct { + // The name of the function. Heap allocated and owned by the FnDebug. + char* name; + + // An array of line numbers. There is one element in this array for each + // bytecode in the function's bytecode array. The value of that element is + // the line in the source code that generated that instruction. + IntBuffer sourceLines; +} FnDebug; + +// A loaded module and the top-level variables it defines. +// +// While this is an Obj and is managed by the GC, it never appears as a +// first-class object in Wren. +typedef struct { + Obj obj; + + // The currently defined top-level variables. + ValueBuffer variables; + + // Symbol table for the names of all module variables. Indexes here directly + // correspond to entries in [variables]. + SymbolTable variableNames; + + // The name of the module. + ObjString* name; +} ObjModule; + +// A function object. It wraps and owns the bytecode and other debug information +// for a callable chunk of code. +// +// Function objects are not passed around and invoked directly. Instead, they +// are always referenced by an [ObjClosure] which is the real first-class +// representation of a function. This isn't strictly necessary if they function +// has no upvalues, but lets the rest of the VM assume all called objects will +// be closures. +typedef struct { + Obj obj; + + ByteBuffer code; + ValueBuffer constants; + + // The module where this function was defined. + ObjModule* module; + + // The maximum number of stack slots this function may use. + int maxSlots; + + // The number of upvalues this function closes over. + int numUpvalues; + + // The number of parameters this function expects. Used to ensure that .call + // handles a mismatch between number of parameters and arguments. This will + // only be set for fns, and not ObjFns that represent methods or scripts. + int arity; + FnDebug* debug; +} ObjFn; + +// An instance of a first-class function and the environment it has closed over. +// Unlike [ObjFn], this has captured the upvalues that the function accesses. +typedef struct { + Obj obj; + + // The function that this closure is an instance of. + ObjFn* fn; + + // The upvalues this function has closed over. + ObjUpvalue* upvalues[FLEXIBLE_ARRAY]; +} ObjClosure; + +typedef struct { + // Pointer to the current (really next-to-be-executed) instruction in the + // function's bytecode. + uint8_t* ip; + + // The closure being executed. + ObjClosure* closure; + + // Pointer to the first stack slot used by this call frame. This will contain + // the receiver, followed by the function's parameters, then local variables + // and temporaries. + Value* stackStart; +} CallFrame; + +// Tracks how this fiber has been invoked, aside from the ways that can be +// detected from the state of other fields in the fiber. +typedef enum { + // The fiber is being run from another fiber using a call to `try()`. + FIBER_TRY, + + // The fiber was directly invoked by `runInterpreter()`. This means it's the + // initial fiber used by a call to `wrenCall()` or `wrenInterpret()`. + FIBER_ROOT, + + // The fiber is invoked some other way. If [caller] is `NULL` then the fiber + // was invoked using `call()`. If [numFrames] is zero, then the fiber has + // finished running and is done. If [numFrames] is one and that frame's `ip` + // points to the first byte of code, the fiber has not been started yet. + FIBER_OTHER, +} FiberState; + +typedef struct sObjFiber { + Obj obj; + + // The stack of value slots. This is used for holding local variables and + // temporaries while the fiber is executing. It is heap-allocated and grown + // as needed. + Value* stack; + + // A pointer to one past the top-most value on the stack. + Value* stackTop; + + // The number of allocated slots in the stack array. + int stackCapacity; + + // The stack of call frames. This is a dynamic array that grows as needed but + // never shrinks. + CallFrame* frames; + + // The number of frames currently in use in [frames]. + int numFrames; + + // The number of [frames] allocated. + int frameCapacity; + + // Pointer to the first node in the linked list of open upvalues that are + // pointing to values still on the stack. The head of the list will be the + // upvalue closest to the top of the stack, and then the list works downwards. + ObjUpvalue* openUpvalues; + + // The fiber that ran this one. If this fiber is yielded, control will resume + // to this one. May be `NULL`. + struct sObjFiber* caller; + + // If the fiber failed because of a runtime error, this will contain the + // error object. Otherwise, it will be null. + Value error; + + FiberState state; +} ObjFiber; + +typedef enum { + // A primitive method implemented in C in the VM. Unlike foreign methods, + // this can directly manipulate the fiber's stack. + METHOD_PRIMITIVE, + + // A primitive that handles .call on Fn. + METHOD_FUNCTION_CALL, + + // A externally-defined C method. + METHOD_FOREIGN, + + // A normal user-defined method. + METHOD_BLOCK, + + // No method for the given symbol. + METHOD_NONE +} MethodType; + +typedef struct { + MethodType type; + + // The method function itself. The [type] determines which field of the union + // is used. + union { + Primitive primitive; + WrenForeignMethodFn foreign; + ObjClosure* closure; + } as; +} Method; + +DECLARE_BUFFER(Method, Method); + +struct sObjClass { + Obj obj; + ObjClass* superclass; + + // The number of fields needed for an instance of this class, including all + // of its superclass fields. + int numFields; + + // The table of methods that are defined in or inherited by this class. + // Methods are called by symbol, and the symbol directly maps to an index in + // this table. This makes method calls fast at the expense of empty cells in + // the list for methods the class doesn't support. + // + // You can think of it as a hash table that never has collisions but has a + // really low load factor. Since methods are pretty small (just a type and a + // pointer), this should be a worthwhile trade-off. + MethodBuffer methods; + + // The name of the class. + ObjString* name; + + // The ClassAttribute for the class, if any + Value attributes; +}; + +typedef struct { + Obj obj; + uint8_t data[FLEXIBLE_ARRAY]; +} ObjForeign; + +typedef struct { + Obj obj; + Value fields[FLEXIBLE_ARRAY]; +} ObjInstance; + +typedef struct { + Obj obj; + + // The elements in the list. + ValueBuffer elements; +} ObjList; + +typedef struct { + // The entry's key, or UNDEFINED_VAL if the entry is not in use. + Value key; + + // The value associated with the key. If the key is UNDEFINED_VAL, this will + // be false to indicate an open available entry or true to indicate a + // tombstone -- an entry that was previously in use but was then deleted. + Value value; +} MapEntry; + +// A hash table mapping keys to values. +// +// We use something very simple: open addressing with linear probing. The hash +// table is an array of entries. Each entry is a key-value pair. If the key is +// the special UNDEFINED_VAL, it indicates no value is currently in that slot. +// Otherwise, it's a valid key, and the value is the value associated with it. +// +// When entries are added, the array is dynamically scaled by GROW_FACTOR to +// keep the number of filled slots under MAP_LOAD_PERCENT. Likewise, if the map +// gets empty enough, it will be resized to a smaller array. When this happens, +// all existing entries are rehashed and re-added to the new array. +// +// When an entry is removed, its slot is replaced with a "tombstone". This is an +// entry whose key is UNDEFINED_VAL and whose value is TRUE_VAL. When probing +// for a key, we will continue past tombstones, because the desired key may be +// found after them if the key that was removed was part of a prior collision. +// When the array gets resized, all tombstones are discarded. +typedef struct { + Obj obj; + + // The number of entries allocated. + uint32_t capacity; + + // The number of entries in the map. + uint32_t count; + + // Pointer to a contiguous array of [capacity] entries. + MapEntry* entries; +} ObjMap; + +typedef struct { + Obj obj; + + // The beginning of the range. + double from; + + // The end of the range. May be greater or less than [from]. + double to; + + // True if [to] is included in the range. + bool isInclusive; +} ObjRange; + +// An IEEE 754 double-precision float is a 64-bit value with bits laid out like: +// +// 1 Sign bit +// | 11 Exponent bits +// | | 52 Mantissa (i.e. fraction) bits +// | | | +// S[Exponent-][Mantissa------------------------------------------] +// +// The details of how these are used to represent numbers aren't really +// relevant here as long we don't interfere with them. The important bit is NaN. +// +// An IEEE double can represent a few magical values like NaN ("not a number"), +// Infinity, and -Infinity. A NaN is any value where all exponent bits are set: +// +// v--NaN bits +// -11111111111---------------------------------------------------- +// +// Here, "-" means "doesn't matter". Any bit sequence that matches the above is +// a NaN. With all of those "-", it obvious there are a *lot* of different +// bit patterns that all mean the same thing. NaN tagging takes advantage of +// this. We'll use those available bit patterns to represent things other than +// numbers without giving up any valid numeric values. +// +// NaN values come in two flavors: "signalling" and "quiet". The former are +// intended to halt execution, while the latter just flow through arithmetic +// operations silently. We want the latter. Quiet NaNs are indicated by setting +// the highest mantissa bit: +// +// v--Highest mantissa bit +// -[NaN ]1--------------------------------------------------- +// +// If all of the NaN bits are set, it's not a number. Otherwise, it is. +// That leaves all of the remaining bits as available for us to play with. We +// stuff a few different kinds of things here: special singleton values like +// "true", "false", and "null", and pointers to objects allocated on the heap. +// We'll use the sign bit to distinguish singleton values from pointers. If +// it's set, it's a pointer. +// +// v--Pointer or singleton? +// S[NaN ]1--------------------------------------------------- +// +// For singleton values, we just enumerate the different values. We'll use the +// low bits of the mantissa for that, and only need a few: +// +// 3 Type bits--v +// 0[NaN ]1------------------------------------------------[T] +// +// For pointers, we are left with 51 bits of mantissa to store an address. +// That's more than enough room for a 32-bit address. Even 64-bit machines +// only actually use 48 bits for addresses, so we've got plenty. We just stuff +// the address right into the mantissa. +// +// Ta-da, double precision numbers, pointers, and a bunch of singleton values, +// all stuffed into a single 64-bit sequence. Even better, we don't have to +// do any masking or work to extract number values: they are unmodified. This +// means math on numbers is fast. +#if WREN_NAN_TAGGING + +// A mask that selects the sign bit. +#define SIGN_BIT ((uint64_t)1 << 63) + +// The bits that must be set to indicate a quiet NaN. +#define QNAN ((uint64_t)0x7ffc000000000000) + +// If the NaN bits are set, it's not a number. +#define IS_NUM(value) (((value)&QNAN) != QNAN) + +// An object pointer is a NaN with a set sign bit. +#define IS_OBJ(value) (((value) & (QNAN | SIGN_BIT)) == (QNAN | SIGN_BIT)) + +#define IS_FALSE(value) ((value) == FALSE_VAL) +#define IS_NULL(value) ((value) == NULL_VAL) +#define IS_UNDEFINED(value) ((value) == UNDEFINED_VAL) + +// Masks out the tag bits used to identify the singleton value. +#define MASK_TAG (7) + +// Tag values for the different singleton values. +#define TAG_NAN (0) +#define TAG_NULL (1) +#define TAG_FALSE (2) +#define TAG_TRUE (3) +#define TAG_UNDEFINED (4) +#define TAG_UNUSED2 (5) +#define TAG_UNUSED3 (6) +#define TAG_UNUSED4 (7) + +// Value -> 0 or 1. +#define AS_BOOL(value) ((value) == TRUE_VAL) + +// Value -> Obj*. +#define AS_OBJ(value) ((Obj*)(uintptr_t)((value) & ~(SIGN_BIT | QNAN))) + +// Singleton values. +#define NULL_VAL ((Value)(uint64_t)(QNAN | TAG_NULL)) +#define FALSE_VAL ((Value)(uint64_t)(QNAN | TAG_FALSE)) +#define TRUE_VAL ((Value)(uint64_t)(QNAN | TAG_TRUE)) +#define UNDEFINED_VAL ((Value)(uint64_t)(QNAN | TAG_UNDEFINED)) + +// Gets the singleton type tag for a Value (which must be a singleton). +#define GET_TAG(value) ((int)((value)&MASK_TAG)) + +#else + +// Value -> 0 or 1. +#define AS_BOOL(value) ((value).type == VAL_TRUE) + +// Value -> Obj*. +#define AS_OBJ(v) ((v).as.obj) + +// Determines if [value] is a garbage-collected object or not. +#define IS_OBJ(value) ((value).type == VAL_OBJ) + +#define IS_FALSE(value) ((value).type == VAL_FALSE) +#define IS_NULL(value) ((value).type == VAL_NULL) +#define IS_NUM(value) ((value).type == VAL_NUM) +#define IS_UNDEFINED(value) ((value).type == VAL_UNDEFINED) + +// Singleton values. +#define FALSE_VAL ((Value){VAL_FALSE, {0}}) +#define NULL_VAL ((Value){VAL_NULL, {0}}) +#define TRUE_VAL ((Value){VAL_TRUE, {0}}) +#define UNDEFINED_VAL ((Value){VAL_UNDEFINED, {0}}) + +#endif + +// Creates a new "raw" class. It has no metaclass or superclass whatsoever. +// This is only used for bootstrapping the initial Object and Class classes, +// which are a little special. +ObjClass* wrenNewSingleClass(WrenVM* vm, int numFields, ObjString* name); + +// Makes [superclass] the superclass of [subclass], and causes subclass to +// inherit its methods. This should be called before any methods are defined +// on subclass. +void wrenBindSuperclass(WrenVM* vm, ObjClass* subclass, ObjClass* superclass); + +// Creates a new class object as well as its associated metaclass. +ObjClass* wrenNewClass(WrenVM* vm, ObjClass* superclass, int numFields, ObjString* name); + +void wrenBindMethod(WrenVM* vm, ObjClass* classObj, int symbol, Method method); + +// Creates a new closure object that invokes [fn]. Allocates room for its +// upvalues, but assumes outside code will populate it. +ObjClosure* wrenNewClosure(WrenVM* vm, ObjFn* fn); + +// Creates a new fiber object that will invoke [closure]. +ObjFiber* wrenNewFiber(WrenVM* vm, ObjClosure* closure); + +// Adds a new [CallFrame] to [fiber] invoking [closure] whose stack starts at +// [stackStart]. +static inline void wrenAppendCallFrame(WrenVM* vm, ObjFiber* fiber, ObjClosure* closure, Value* stackStart) { + // The caller should have ensured we already have enough capacity. + ASSERT(fiber->frameCapacity > fiber->numFrames, "No memory for call frame."); + + CallFrame* frame = &fiber->frames[fiber->numFrames++]; + frame->stackStart = stackStart; + frame->closure = closure; + frame->ip = closure->fn->code.data; +} + +// Ensures [fiber]'s stack has at least [needed] slots. +void wrenEnsureStack(WrenVM* vm, ObjFiber* fiber, int needed); + +static inline bool wrenHasError(const ObjFiber* fiber) { + return !IS_NULL(fiber->error); +} + +ObjForeign* wrenNewForeign(WrenVM* vm, ObjClass* classObj, size_t size); + +// Creates a new empty function. Before being used, it must have code, +// constants, etc. added to it. +ObjFn* wrenNewFunction(WrenVM* vm, ObjModule* module, int maxSlots); + +void wrenFunctionBindName(WrenVM* vm, ObjFn* fn, const char* name, int length); + +// Creates a new instance of the given [classObj]. +Value wrenNewInstance(WrenVM* vm, ObjClass* classObj); + +// Creates a new list with [numElements] elements (which are left +// uninitialized.) +ObjList* wrenNewList(WrenVM* vm, uint32_t numElements); + +// Inserts [value] in [list] at [index], shifting down the other elements. +void wrenListInsert(WrenVM* vm, ObjList* list, Value value, uint32_t index); + +// Removes and returns the item at [index] from [list]. +Value wrenListRemoveAt(WrenVM* vm, ObjList* list, uint32_t index); + +// Searches for [value] in [list], returns the index or -1 if not found. +int wrenListIndexOf(WrenVM* vm, ObjList* list, Value value); + +// Creates a new empty map. +ObjMap* wrenNewMap(WrenVM* vm); + +// Validates that [arg] is a valid object for use as a map key. Returns true if +// it is and returns false otherwise. Use validateKey usually, for a runtime error. +// This separation exists to aid the API in surfacing errors to the developer as well. +static inline bool wrenMapIsValidKey(Value arg); + +// Looks up [key] in [map]. If found, returns the value. Otherwise, returns +// `UNDEFINED_VAL`. +Value wrenMapGet(ObjMap* map, Value key); + +// Associates [key] with [value] in [map]. +void wrenMapSet(WrenVM* vm, ObjMap* map, Value key, Value value); + +void wrenMapClear(WrenVM* vm, ObjMap* map); + +// Removes [key] from [map], if present. Returns the value for the key if found +// or `NULL_VAL` otherwise. +Value wrenMapRemoveKey(WrenVM* vm, ObjMap* map, Value key); + +// Creates a new module. +ObjModule* wrenNewModule(WrenVM* vm, ObjString* name); + +// Creates a new range from [from] to [to]. +Value wrenNewRange(WrenVM* vm, double from, double to, bool isInclusive); + +// Creates a new string object and copies [text] into it. +// +// [text] must be non-NULL. +Value wrenNewString(WrenVM* vm, const char* text); + +// Creates a new string object of [length] and copies [text] into it. +// +// [text] may be NULL if [length] is zero. +Value wrenNewStringLength(WrenVM* vm, const char* text, size_t length); + +// Creates a new string object by taking a range of characters from [source]. +// The range starts at [start], contains [count] bytes, and increments by +// [step]. +Value wrenNewStringFromRange(WrenVM* vm, ObjString* source, int start, uint32_t count, int step); + +// Produces a string representation of [value]. +Value wrenNumToString(WrenVM* vm, double value); + +// Creates a new formatted string from [format] and any additional arguments +// used in the format string. +// +// This is a very restricted flavor of formatting, intended only for internal +// use by the VM. Two formatting characters are supported, each of which reads +// the next argument as a certain type: +// +// $ - A C string. +// @ - A Wren string object. +Value wrenStringFormat(WrenVM* vm, const char* format, ...); + +// Creates a new string containing the UTF-8 encoding of [value]. +Value wrenStringFromCodePoint(WrenVM* vm, int value); + +// Creates a new string from the integer representation of a byte +Value wrenStringFromByte(WrenVM* vm, uint8_t value); + +// Creates a new string containing the code point in [string] starting at byte +// [index]. If [index] points into the middle of a UTF-8 sequence, returns an +// empty string. +Value wrenStringCodePointAt(WrenVM* vm, ObjString* string, uint32_t index); + +// Search for the first occurence of [needle] within [haystack] and returns its +// zero-based offset. Returns `UINT32_MAX` if [haystack] does not contain +// [needle]. +uint32_t wrenStringFind(ObjString* haystack, ObjString* needle, uint32_t startIndex); + +// Returns true if [a] and [b] represent the same string. +static inline bool wrenStringEqualsCString(const ObjString* a, const char* b, size_t length) { + return a->length == length && memcmp(a->value, b, length) == 0; +} + +// Creates a new open upvalue pointing to [value] on the stack. +ObjUpvalue* wrenNewUpvalue(WrenVM* vm, Value* value); + +// Mark [obj] as reachable and still in use. This should only be called +// during the sweep phase of a garbage collection. +void wrenGrayObj(WrenVM* vm, Obj* obj); + +// Mark [value] as reachable and still in use. This should only be called +// during the sweep phase of a garbage collection. +void wrenGrayValue(WrenVM* vm, Value value); + +// Mark the values in [buffer] as reachable and still in use. This should only +// be called during the sweep phase of a garbage collection. +void wrenGrayBuffer(WrenVM* vm, ValueBuffer* buffer); + +// Processes every object in the gray stack until all reachable objects have +// been marked. After that, all objects are either white (freeable) or black +// (in use and fully traversed). +void wrenBlackenObjects(WrenVM* vm); + +// Releases all memory owned by [obj], including [obj] itself. +void wrenFreeObj(WrenVM* vm, Obj* obj); + +// Returns the class of [value]. +// +// Unlike wrenGetClassInline in wren_vm.h, this is not inlined. Inlining helps +// performance (significantly) in some cases, but degrades it in others. The +// ones used by the implementation were chosen to give the best results in the +// benchmarks. +ObjClass* wrenGetClass(WrenVM* vm, Value value); + +// Returns true if [a] and [b] are strictly the same value. This is identity +// for object values, and value equality for unboxed values. +static inline bool wrenValuesSame(Value a, Value b) { +#if WREN_NAN_TAGGING + // Value types have unique bit representations and we compare object types + // by identity (i.e. pointer), so all we need to do is compare the bits. + return a == b; +#else + if (a.type != b.type) return false; + if (a.type == VAL_NUM) return a.as.num == b.as.num; + return a.as.obj == b.as.obj; +#endif +} + +// Returns true if [a] and [b] are equivalent. Immutable values (null, bools, +// numbers, ranges, and strings) are equal if they have the same data. All +// other values are equal if they are identical objects. +bool wrenValuesEqual(Value a, Value b); + +// Returns true if [value] is a bool. Do not call this directly, instead use +// [IS_BOOL]. +static inline bool wrenIsBool(Value value) { +#if WREN_NAN_TAGGING + return value == TRUE_VAL || value == FALSE_VAL; +#else + return value.type == VAL_FALSE || value.type == VAL_TRUE; +#endif +} + +// Returns true if [value] is an object of type [type]. Do not call this +// directly, instead use the [IS___] macro for the type in question. +static inline bool wrenIsObjType(Value value, ObjType type) { + return IS_OBJ(value) && AS_OBJ(value)->type == type; +} + +// Converts the raw object pointer [obj] to a [Value]. +static inline Value wrenObjectToValue(Obj* obj) { +#if WREN_NAN_TAGGING + // The triple casting is necessary here to satisfy some compilers: + // 1. (uintptr_t) Convert the pointer to a number of the right size. + // 2. (uint64_t) Pad it up to 64 bits in 32-bit builds. + // 3. Or in the bits to make a tagged Nan. + // 4. Cast to a typedef'd value. + return (Value)(SIGN_BIT | QNAN | (uint64_t)(uintptr_t)(obj)); +#else + Value value; + value.type = VAL_OBJ; + value.as.obj = obj; + return value; +#endif +} + +// Interprets [value] as a [double]. +static inline double wrenValueToNum(Value value) { +#if WREN_NAN_TAGGING + return wrenDoubleFromBits(value); +#else + return value.as.num; +#endif +} + +// Converts [num] to a [Value]. +static inline Value wrenNumToValue(double num) { +#if WREN_NAN_TAGGING + return wrenDoubleToBits(num); +#else + Value value; + value.type = VAL_NUM; + value.as.num = num; + return value; +#endif +} + +static inline bool wrenMapIsValidKey(Value arg) { + return IS_BOOL(arg) || IS_CLASS(arg) || IS_NULL(arg) || IS_NUM(arg) || IS_RANGE(arg) || IS_STRING(arg); +} + +#endif +// End file "wren_value.h" +// Begin file "wren_vm.h" +#ifndef wren_vm_h +#define wren_vm_h + +// Begin file "wren_compiler.h" +#ifndef wren_compiler_h +#define wren_compiler_h + +typedef struct sCompiler Compiler; + +// This module defines the compiler for Wren. It takes a string of source code +// and lexes, parses, and compiles it. Wren uses a single-pass compiler. It +// does not build an actual AST during parsing and then consume that to +// generate code. Instead, the parser directly emits bytecode. +// +// This forces a few restrictions on the grammar and semantics of the language. +// Things like forward references and arbitrary lookahead are much harder. We +// get a lot in return for that, though. +// +// The implementation is much simpler since we don't need to define a bunch of +// AST data structures. More so, we don't have to deal with managing memory for +// AST objects. The compiler does almost no dynamic allocation while running. +// +// Compilation is also faster since we don't create a bunch of temporary data +// structures and destroy them after generating code. + +// Compiles [source], a string of Wren source code located in [module], to an +// [ObjFn] that will execute that code when invoked. Returns `NULL` if the +// source contains any syntax errors. +// +// If [isExpression] is `true`, [source] should be a single expression, and +// this compiles it to a function that evaluates and returns that expression. +// Otherwise, [source] should be a series of top level statements. +// +// If [printErrors] is `true`, any compile errors are output to stderr. +// Otherwise, they are silently discarded. +ObjFn* wrenCompile(WrenVM* vm, ObjModule* module, const char* source, bool isExpression, bool printErrors); + +// When a class is defined, its superclass is not known until runtime since +// class definitions are just imperative statements. Most of the bytecode for a +// a method doesn't care, but there are two places where it matters: +// +// - To load or store a field, we need to know the index of the field in the +// instance's field array. We need to adjust this so that subclass fields +// are positioned after superclass fields, and we don't know this until the +// superclass is known. +// +// - Superclass calls need to know which superclass to dispatch to. +// +// We could handle this dynamically, but that adds overhead. Instead, when a +// method is bound, we walk the bytecode for the function and patch it up. +void wrenBindMethodCode(ObjClass* classObj, ObjFn* fn); + +// Reaches all of the heap-allocated objects in use by [compiler] (and all of +// its parents) so that they are not collected by the GC. +void wrenMarkCompiler(WrenVM* vm, Compiler* compiler); + +#endif +// End file "wren_compiler.h" + +// The maximum number of temporary objects that can be made visible to the GC +// at one time. +#define WREN_MAX_TEMP_ROOTS 8 + +typedef enum { +#define OPCODE(name, _) CODE_##name, + // Begin file "wren_opcodes.h" + // This defines the bytecode instructions used by the VM. It does so by invoking + // an OPCODE() macro which is expected to be defined at the point that this is + // included. (See: http://en.wikipedia.org/wiki/X_Macro for more.) + // + // The first argument is the name of the opcode. The second is its "stack + // effect" -- the amount that the op code changes the size of the stack. A + // stack effect of 1 means it pushes a value and the stack grows one larger. + // -2 means it pops two values, etc. + // + // Note that the order of instructions here affects the order of the dispatch + // table in the VM's interpreter loop. That in turn affects caching which + // affects overall performance. Take care to run benchmarks if you change the + // order here. + + // Load the constant at index [arg]. + OPCODE(CONSTANT, 1) + + // Push null onto the stack. + OPCODE(NULL, 1) + + // Push false onto the stack. + OPCODE(FALSE, 1) + + // Push true onto the stack. + OPCODE(TRUE, 1) + + // Pushes the value in the given local slot. + OPCODE(LOAD_LOCAL_0, 1) OPCODE(LOAD_LOCAL_1, 1) OPCODE(LOAD_LOCAL_2, 1) OPCODE(LOAD_LOCAL_3, 1) + OPCODE(LOAD_LOCAL_4, 1) OPCODE(LOAD_LOCAL_5, 1) OPCODE(LOAD_LOCAL_6, 1) OPCODE(LOAD_LOCAL_7, 1) + OPCODE(LOAD_LOCAL_8, 1) + + // Note: The compiler assumes the following _STORE instructions always + // immediately follow their corresponding _LOAD ones. + + // Pushes the value in local slot [arg]. + OPCODE(LOAD_LOCAL, 1) + + // Stores the top of stack in local slot [arg]. Does not pop it. + OPCODE(STORE_LOCAL, 0) + + // Pushes the value in upvalue [arg]. + OPCODE(LOAD_UPVALUE, 1) + + // Stores the top of stack in upvalue [arg]. Does not pop it. + OPCODE(STORE_UPVALUE, 0) + + // Pushes the value of the top-level variable in slot [arg]. + OPCODE(LOAD_MODULE_VAR, 1) + + // Stores the top of stack in top-level variable slot [arg]. Does not pop it. + OPCODE(STORE_MODULE_VAR, 0) + + // Pushes the value of the field in slot [arg] of the receiver of the current + // function. This is used for regular field accesses on "this" directly in + // methods. This instruction is faster than the more general CODE_LOAD_FIELD + // instruction. + OPCODE(LOAD_FIELD_THIS, 1) + + // Stores the top of the stack in field slot [arg] in the receiver of the + // current value. Does not pop the value. This instruction is faster than the + // more general CODE_LOAD_FIELD instruction. + OPCODE(STORE_FIELD_THIS, 0) + + // Pops an instance and pushes the value of the field in slot [arg] of it. + OPCODE(LOAD_FIELD, 0) + + // Pops an instance and stores the subsequent top of stack in field slot + // [arg] in it. Does not pop the value. + OPCODE(STORE_FIELD, -1) + + // Pop and discard the top of stack. + OPCODE(POP, -1) + + // Invoke the method with symbol [arg]. The number indicates the number of + // arguments (not including the receiver). + OPCODE(CALL_0, 0) OPCODE(CALL_1, -1) OPCODE(CALL_2, -2) OPCODE(CALL_3, -3) OPCODE(CALL_4, -4) OPCODE(CALL_5, -5) + OPCODE(CALL_6, -6) OPCODE(CALL_7, -7) OPCODE(CALL_8, -8) OPCODE(CALL_9, -9) OPCODE(CALL_10, -10) + OPCODE(CALL_11, -11) OPCODE(CALL_12, -12) OPCODE(CALL_13, -13) OPCODE(CALL_14, -14) + OPCODE(CALL_15, -15) OPCODE(CALL_16, -16) + + // Invoke a superclass method with symbol [arg]. The number indicates the + // number of arguments (not including the receiver). + OPCODE(SUPER_0, 0) OPCODE(SUPER_1, -1) OPCODE(SUPER_2, -2) OPCODE(SUPER_3, -3) OPCODE(SUPER_4, -4) + OPCODE(SUPER_5, -5) OPCODE(SUPER_6, -6) OPCODE(SUPER_7, -7) OPCODE(SUPER_8, -8) OPCODE(SUPER_9, -9) + OPCODE(SUPER_10, -10) OPCODE(SUPER_11, -11) OPCODE(SUPER_12, -12) OPCODE(SUPER_13, -13) + OPCODE(SUPER_14, -14) OPCODE(SUPER_15, -15) OPCODE(SUPER_16, -16) + + // Jump the instruction pointer [arg] forward. + OPCODE(JUMP, 0) + + // Jump the instruction pointer [arg] backward. + OPCODE(LOOP, 0) + + // Pop and if not truthy then jump the instruction pointer [arg] forward. + OPCODE(JUMP_IF, -1) + + // If the top of the stack is false, jump [arg] forward. Otherwise, pop and + // continue. + OPCODE(AND, -1) + + // If the top of the stack is non-false, jump [arg] forward. Otherwise, pop + // and continue. + OPCODE(OR, -1) + + // Close the upvalue for the local on the top of the stack, then pop it. + OPCODE(CLOSE_UPVALUE, -1) + + // Exit from the current function and return the value on the top of the + // stack. + OPCODE(RETURN, 0) + + // Creates a closure for the function stored at [arg] in the constant table. + // + // Following the function argument is a number of arguments, two for each + // upvalue. The first is true if the variable being captured is a local (as + // opposed to an upvalue), and the second is the index of the local or + // upvalue being captured. + // + // Pushes the created closure. + OPCODE(CLOSURE, 1) + + // Creates a new instance of a class. + // + // Assumes the class object is in slot zero, and replaces it with the new + // uninitialized instance of that class. This opcode is only emitted by the + // compiler-generated constructor metaclass methods. + OPCODE(CONSTRUCT, 0) + + // Creates a new instance of a foreign class. + // + // Assumes the class object is in slot zero, and replaces it with the new + // uninitialized instance of that class. This opcode is only emitted by the + // compiler-generated constructor metaclass methods. + OPCODE(FOREIGN_CONSTRUCT, 0) + + // Creates a class. Top of stack is the superclass. Below that is a string for + // the name of the class. Byte [arg] is the number of fields in the class. + OPCODE(CLASS, -1) + + // Ends a class. + // Atm the stack contains the class and the ClassAttributes (or null). + OPCODE(END_CLASS, -2) + + // Creates a foreign class. Top of stack is the superclass. Below that is a + // string for the name of the class. + OPCODE(FOREIGN_CLASS, -1) + + // Define a method for symbol [arg]. The class receiving the method is popped + // off the stack, then the function defining the body is popped. + // + // If a foreign method is being defined, the "function" will be a string + // identifying the foreign method. Otherwise, it will be a function or + // closure. + OPCODE(METHOD_INSTANCE, -2) + + // Define a method for symbol [arg]. The class whose metaclass will receive + // the method is popped off the stack, then the function defining the body is + // popped. + // + // If a foreign method is being defined, the "function" will be a string + // identifying the foreign method. Otherwise, it will be a function or + // closure. + OPCODE(METHOD_STATIC, -2) + + // This is executed at the end of the module's body. Pushes NULL onto the stack + // as the "return value" of the import statement and stores the module as the + // most recently imported one. + OPCODE(END_MODULE, 1) + + // Import a module whose name is the string stored at [arg] in the constant + // table. + // + // Pushes null onto the stack so that the fiber for the imported module can + // replace that with a dummy value when it returns. (Fibers always return a + // value when resuming a caller.) + OPCODE(IMPORT_MODULE, 1) + + // Import a variable from the most recently imported module. The name of the + // variable to import is at [arg] in the constant table. Pushes the loaded + // variable's value. + OPCODE(IMPORT_VARIABLE, 1) + + // This pseudo-instruction indicates the end of the bytecode. It should + // always be preceded by a `CODE_RETURN`, so is never actually executed. + OPCODE(END, 0) +// End file "wren_opcodes.h" +#undef OPCODE +} Code; + +// A handle to a value, basically just a linked list of extra GC roots. +// +// Note that even non-heap-allocated values can be stored here. +struct WrenHandle { + Value value; + + WrenHandle* prev; + WrenHandle* next; +}; + +struct WrenVM { + ObjClass* boolClass; + ObjClass* classClass; + ObjClass* fiberClass; + ObjClass* fnClass; + ObjClass* listClass; + ObjClass* mapClass; + ObjClass* nullClass; + ObjClass* numClass; + ObjClass* objectClass; + ObjClass* rangeClass; + ObjClass* stringClass; + + // The fiber that is currently running. + ObjFiber* fiber; + + // The loaded modules. Each key is an ObjString (except for the main module, + // whose key is null) for the module's name and the value is the ObjModule + // for the module. + ObjMap* modules; + + // The most recently imported module. More specifically, the module whose + // code has most recently finished executing. + // + // Not treated like a GC root since the module is already in [modules]. + ObjModule* lastModule; + + // Memory management data: + + // The number of bytes that are known to be currently allocated. Includes all + // memory that was proven live after the last GC, as well as any new bytes + // that were allocated since then. Does *not* include bytes for objects that + // were freed since the last GC. + size_t bytesAllocated; + + // The number of total allocated bytes that will trigger the next GC. + size_t nextGC; + + // The first object in the linked list of all currently allocated objects. + Obj* first; + + // The "gray" set for the garbage collector. This is the stack of unprocessed + // objects while a garbage collection pass is in process. + Obj** gray; + int grayCount; + int grayCapacity; + + // The list of temporary roots. This is for temporary or new objects that are + // not otherwise reachable but should not be collected. + // + // They are organized as a stack of pointers stored in this array. This + // implies that temporary roots need to have stack semantics: only the most + // recently pushed object can be released. + Obj* tempRoots[WREN_MAX_TEMP_ROOTS]; + + int numTempRoots; + + // Pointer to the first node in the linked list of active handles or NULL if + // there are none. + WrenHandle* handles; + + // Pointer to the bottom of the range of stack slots available for use from + // the C API. During a foreign method, this will be in the stack of the fiber + // that is executing a method. + // + // If not in a foreign method, this is initially NULL. If the user requests + // slots by calling wrenEnsureSlots(), a stack is created and this is + // initialized. + Value* apiStack; + + WrenConfiguration config; + + // Compiler and debugger data: + + // The compiler that is currently compiling code. This is used so that heap + // allocated objects used by the compiler can be found if a GC is kicked off + // in the middle of a compile. + Compiler* compiler; + + // There is a single global symbol table for all method names on all classes. + // Method calls are dispatched directly by index in this table. + SymbolTable methodNames; +}; + +// A generic allocation function that handles all explicit memory management. +// It's used like so: +// +// - To allocate new memory, [memory] is NULL and [oldSize] is zero. It should +// return the allocated memory or NULL on failure. +// +// - To attempt to grow an existing allocation, [memory] is the memory, +// [oldSize] is its previous size, and [newSize] is the desired size. +// It should return [memory] if it was able to grow it in place, or a new +// pointer if it had to move it. +// +// - To shrink memory, [memory], [oldSize], and [newSize] are the same as above +// but it will always return [memory]. +// +// - To free memory, [memory] will be the memory to free and [newSize] and +// [oldSize] will be zero. It should return NULL. +void* wrenReallocate(WrenVM* vm, void* memory, size_t oldSize, size_t newSize); + +// Invoke the finalizer for the foreign object referenced by [foreign]. +void wrenFinalizeForeign(WrenVM* vm, ObjForeign* foreign); + +// Creates a new [WrenHandle] for [value]. +WrenHandle* wrenMakeHandle(WrenVM* vm, Value value); + +// Compile [source] in the context of [module] and wrap in a fiber that can +// execute it. +// +// Returns NULL if a compile error occurred. +ObjClosure* wrenCompileSource(WrenVM* vm, const char* module, const char* source, bool isExpression, bool printErrors); + +// Looks up a variable from a previously-loaded module. +// +// Aborts the current fiber if the module or variable could not be found. +Value wrenGetModuleVariable(WrenVM* vm, Value moduleName, Value variableName); + +// Returns the value of the module-level variable named [name] in the main +// module. +Value wrenFindVariable(WrenVM* vm, ObjModule* module, const char* name); + +// Adds a new implicitly declared top-level variable named [name] to [module] +// based on a use site occurring on [line]. +// +// Does not check to see if a variable with that name is already declared or +// defined. Returns the symbol for the new variable or -2 if there are too many +// variables defined. +int wrenDeclareVariable(WrenVM* vm, ObjModule* module, const char* name, size_t length, int line); + +// Adds a new top-level variable named [name] to [module], and optionally +// populates line with the line of the implicit first use (line can be NULL). +// +// Returns the symbol for the new variable, -1 if a variable with the given name +// is already defined, or -2 if there are too many variables defined. +// Returns -3 if this is a top-level lowercase variable (localname) that was +// used before being defined. +int wrenDefineVariable(WrenVM* vm, ObjModule* module, const char* name, size_t length, Value value, int* line); + +// Pushes [closure] onto [fiber]'s callstack to invoke it. Expects [numArgs] +// arguments (including the receiver) to be on the top of the stack already. +static inline void wrenCallFunction(WrenVM* vm, ObjFiber* fiber, ObjClosure* closure, int numArgs) { + // Grow the call frame array if needed. + if (fiber->numFrames + 1 > fiber->frameCapacity) { + int max = fiber->frameCapacity * 2; + fiber->frames = (CallFrame*)wrenReallocate(vm, fiber->frames, sizeof(CallFrame) * fiber->frameCapacity, + sizeof(CallFrame) * max); + fiber->frameCapacity = max; + } + + // Grow the stack if needed. + int stackSize = (int)(fiber->stackTop - fiber->stack); + int needed = stackSize + closure->fn->maxSlots; + wrenEnsureStack(vm, fiber, needed); + + wrenAppendCallFrame(vm, fiber, closure, fiber->stackTop - numArgs); +} + +// Marks [obj] as a GC root so that it doesn't get collected. +void wrenPushRoot(WrenVM* vm, Obj* obj); + +// Removes the most recently pushed temporary root. +void wrenPopRoot(WrenVM* vm); + +// Returns the class of [value]. +// +// Defined here instead of in wren_value.h because it's critical that this be +// inlined. That means it must be defined in the header, but the wren_value.h +// header doesn't have a full definitely of WrenVM yet. +static inline ObjClass* wrenGetClassInline(WrenVM* vm, Value value) { + if (IS_NUM(value)) return vm->numClass; + if (IS_OBJ(value)) return AS_OBJ(value)->classObj; + +#if WREN_NAN_TAGGING + switch (GET_TAG(value)) { + case TAG_FALSE: + return vm->boolClass; + break; + case TAG_NAN: + return vm->numClass; + break; + case TAG_NULL: + return vm->nullClass; + break; + case TAG_TRUE: + return vm->boolClass; + break; + case TAG_UNDEFINED: + UNREACHABLE(); + } +#else + switch (value.type) { + case VAL_FALSE: + return vm->boolClass; + case VAL_NULL: + return vm->nullClass; + case VAL_NUM: + return vm->numClass; + case VAL_TRUE: + return vm->boolClass; + case VAL_OBJ: + return AS_OBJ(value)->classObj; + case VAL_UNDEFINED: + UNREACHABLE(); + } +#endif + + UNREACHABLE(); + return NULL; +} + +// Returns `true` if [name] is a local variable name (starts with a lowercase +// letter). +static inline bool wrenIsLocalName(const char* name) { + return name[0] >= 'a' && name[0] <= 'z'; +} + +static inline bool wrenIsFalsyValue(Value value) { + return IS_FALSE(value) || IS_NULL(value); +} + +#endif +// End file "wren_vm.h" + +// Prints the stack trace for the current fiber. +// +// Used when a fiber throws a runtime error which is not caught. +void wrenDebugPrintStackTrace(WrenVM* vm); + +// The "dump" functions are used for debugging Wren itself. Normal code paths +// will not call them unless one of the various DEBUG_ flags is enabled. + +// Prints a representation of [value] to stdout. +void wrenDumpValue(Value value); + +// Prints a representation of the bytecode for [fn] at instruction [i]. +int wrenDumpInstruction(WrenVM* vm, ObjFn* fn, int i); + +// Prints the disassembled code for [fn] to stdout. +void wrenDumpCode(WrenVM* vm, ObjFn* fn); + +// Prints the contents of the current stack for [fiber] to stdout. +void wrenDumpStack(ObjFiber* fiber); + +#endif +// End file "wren_debug.h" +// Begin file "wren_value.c" +#include +#include +#include +#include + +#if WREN_DEBUG_TRACE_MEMORY +#endif + +// TODO: Tune these. +// The initial (and minimum) capacity of a non-empty list or map object. +#define MIN_CAPACITY 16 + +// The rate at which a collection's capacity grows when the size exceeds the +// current capacity. The new capacity will be determined by *multiplying* the +// old capacity by this. Growing geometrically is necessary to ensure that +// adding to a collection has O(1) amortized complexity. +#define GROW_FACTOR 2 + +// The maximum percentage of map entries that can be filled before the map is +// grown. A lower load takes more memory but reduces collisions which makes +// lookup faster. +#define MAP_LOAD_PERCENT 75 + +// The number of call frames initially allocated when a fiber is created. Making +// this smaller makes fibers use less memory (at first) but spends more time +// reallocating when the call stack grows. +#define INITIAL_CALL_FRAMES 4 + +DEFINE_BUFFER(Value, Value); +DEFINE_BUFFER(Method, Method); + +static void initObj(WrenVM* vm, Obj* obj, ObjType type, ObjClass* classObj) { + obj->type = type; + obj->isDark = false; + obj->classObj = classObj; + obj->next = vm->first; + vm->first = obj; +} + +ObjClass* wrenNewSingleClass(WrenVM* vm, int numFields, ObjString* name) { + ObjClass* classObj = ALLOCATE(vm, ObjClass); + initObj(vm, &classObj->obj, OBJ_CLASS, NULL); + classObj->superclass = NULL; + classObj->numFields = numFields; + classObj->name = name; + classObj->attributes = NULL_VAL; + + wrenPushRoot(vm, (Obj*)classObj); + wrenMethodBufferInit(&classObj->methods); + wrenPopRoot(vm); + + return classObj; +} + +void wrenBindSuperclass(WrenVM* vm, ObjClass* subclass, ObjClass* superclass) { + ASSERT(superclass != NULL, "Must have superclass."); + + subclass->superclass = superclass; + + // Include the superclass in the total number of fields. + if (subclass->numFields != -1) { + subclass->numFields += superclass->numFields; + } else { + ASSERT(superclass->numFields == 0, "A foreign class cannot inherit from a class with fields."); + } + + // Inherit methods from its superclass. + for (int i = 0; i < superclass->methods.count; i++) { + wrenBindMethod(vm, subclass, i, superclass->methods.data[i]); + } +} + +ObjClass* wrenNewClass(WrenVM* vm, ObjClass* superclass, int numFields, ObjString* name) { + // Create the metaclass. + Value metaclassName = wrenStringFormat(vm, "@ metaclass", OBJ_VAL(name)); + wrenPushRoot(vm, AS_OBJ(metaclassName)); + + ObjClass* metaclass = wrenNewSingleClass(vm, 0, AS_STRING(metaclassName)); + metaclass->obj.classObj = vm->classClass; + + wrenPopRoot(vm); + + // Make sure the metaclass isn't collected when we allocate the class. + wrenPushRoot(vm, (Obj*)metaclass); + + // Metaclasses always inherit Class and do not parallel the non-metaclass + // hierarchy. + wrenBindSuperclass(vm, metaclass, vm->classClass); + + ObjClass* classObj = wrenNewSingleClass(vm, numFields, name); + + // Make sure the class isn't collected while the inherited methods are being + // bound. + wrenPushRoot(vm, (Obj*)classObj); + + classObj->obj.classObj = metaclass; + wrenBindSuperclass(vm, classObj, superclass); + + wrenPopRoot(vm); + wrenPopRoot(vm); + + return classObj; +} + +void wrenBindMethod(WrenVM* vm, ObjClass* classObj, int symbol, Method method) { + // Make sure the buffer is big enough to contain the symbol's index. + if (symbol >= classObj->methods.count) { + Method noMethod; + noMethod.type = METHOD_NONE; + wrenMethodBufferFill(vm, &classObj->methods, noMethod, symbol - classObj->methods.count + 1); + } + + classObj->methods.data[symbol] = method; +} + +ObjClosure* wrenNewClosure(WrenVM* vm, ObjFn* fn) { + ObjClosure* closure = ALLOCATE_FLEX(vm, ObjClosure, ObjUpvalue*, fn->numUpvalues); + initObj(vm, &closure->obj, OBJ_CLOSURE, vm->fnClass); + + closure->fn = fn; + + // Clear the upvalue array. We need to do this in case a GC is triggered + // after the closure is created but before the upvalue array is populated. + for (int i = 0; i < fn->numUpvalues; i++) closure->upvalues[i] = NULL; + + return closure; +} + +ObjFiber* wrenNewFiber(WrenVM* vm, ObjClosure* closure) { + // Allocate the arrays before the fiber in case it triggers a GC. + CallFrame* frames = ALLOCATE_ARRAY(vm, CallFrame, INITIAL_CALL_FRAMES); + + // Add one slot for the unused implicit receiver slot that the compiler + // assumes all functions have. + int stackCapacity = closure == NULL ? 1 : wrenPowerOf2Ceil(closure->fn->maxSlots + 1); + Value* stack = ALLOCATE_ARRAY(vm, Value, stackCapacity); + + ObjFiber* fiber = ALLOCATE(vm, ObjFiber); + initObj(vm, &fiber->obj, OBJ_FIBER, vm->fiberClass); + + fiber->stack = stack; + fiber->stackTop = fiber->stack; + fiber->stackCapacity = stackCapacity; + + fiber->frames = frames; + fiber->frameCapacity = INITIAL_CALL_FRAMES; + fiber->numFrames = 0; + + fiber->openUpvalues = NULL; + fiber->caller = NULL; + fiber->error = NULL_VAL; + fiber->state = FIBER_OTHER; + + if (closure != NULL) { + // Initialize the first call frame. + wrenAppendCallFrame(vm, fiber, closure, fiber->stack); + + // The first slot always holds the closure. + fiber->stackTop[0] = OBJ_VAL(closure); + fiber->stackTop++; + } + + return fiber; +} + +void wrenEnsureStack(WrenVM* vm, ObjFiber* fiber, int needed) { + if (fiber->stackCapacity >= needed) return; + + int capacity = wrenPowerOf2Ceil(needed); + + Value* oldStack = fiber->stack; + fiber->stack = + (Value*)wrenReallocate(vm, fiber->stack, sizeof(Value) * fiber->stackCapacity, sizeof(Value) * capacity); + fiber->stackCapacity = capacity; + + // If the reallocation moves the stack, then we need to recalculate every + // pointer that points into the old stack to into the same relative distance + // in the new stack. We have to be a little careful about how these are + // calculated because pointer subtraction is only well-defined within a + // single array, hence the slightly redundant-looking arithmetic below. + if (fiber->stack != oldStack) { + // Top of the stack. + if (vm->apiStack >= oldStack && vm->apiStack <= fiber->stackTop) { + vm->apiStack = fiber->stack + (vm->apiStack - oldStack); + } + + // Stack pointer for each call frame. + for (int i = 0; i < fiber->numFrames; i++) { + CallFrame* frame = &fiber->frames[i]; + frame->stackStart = fiber->stack + (frame->stackStart - oldStack); + } + + // Open upvalues. + for (ObjUpvalue* upvalue = fiber->openUpvalues; upvalue != NULL; upvalue = upvalue->next) { + upvalue->value = fiber->stack + (upvalue->value - oldStack); + } + + fiber->stackTop = fiber->stack + (fiber->stackTop - oldStack); + } +} + +ObjForeign* wrenNewForeign(WrenVM* vm, ObjClass* classObj, size_t size) { + ObjForeign* object = ALLOCATE_FLEX(vm, ObjForeign, uint8_t, size); + initObj(vm, &object->obj, OBJ_FOREIGN, classObj); + + // Zero out the bytes. + memset(object->data, 0, size); + return object; +} + +ObjFn* wrenNewFunction(WrenVM* vm, ObjModule* module, int maxSlots) { + FnDebug* debug = ALLOCATE(vm, FnDebug); + debug->name = NULL; + wrenIntBufferInit(&debug->sourceLines); + + ObjFn* fn = ALLOCATE(vm, ObjFn); + initObj(vm, &fn->obj, OBJ_FN, vm->fnClass); + + wrenValueBufferInit(&fn->constants); + wrenByteBufferInit(&fn->code); + fn->module = module; + fn->maxSlots = maxSlots; + fn->numUpvalues = 0; + fn->arity = 0; + fn->debug = debug; + + return fn; +} + +void wrenFunctionBindName(WrenVM* vm, ObjFn* fn, const char* name, int length) { + fn->debug->name = ALLOCATE_ARRAY(vm, char, length + 1); + memcpy(fn->debug->name, name, length); + fn->debug->name[length] = '\0'; +} + +Value wrenNewInstance(WrenVM* vm, ObjClass* classObj) { + ObjInstance* instance = ALLOCATE_FLEX(vm, ObjInstance, Value, classObj->numFields); + initObj(vm, &instance->obj, OBJ_INSTANCE, classObj); + + // Initialize fields to null. + for (int i = 0; i < classObj->numFields; i++) { + instance->fields[i] = NULL_VAL; + } + + return OBJ_VAL(instance); +} + +ObjList* wrenNewList(WrenVM* vm, uint32_t numElements) { + // Allocate this before the list object in case it triggers a GC which would + // free the list. + Value* elements = NULL; + if (numElements > 0) { + elements = ALLOCATE_ARRAY(vm, Value, numElements); + } + + ObjList* list = ALLOCATE(vm, ObjList); + initObj(vm, &list->obj, OBJ_LIST, vm->listClass); + list->elements.capacity = numElements; + list->elements.count = numElements; + list->elements.data = elements; + return list; +} + +void wrenListInsert(WrenVM* vm, ObjList* list, Value value, uint32_t index) { + if (IS_OBJ(value)) wrenPushRoot(vm, AS_OBJ(value)); + + // Add a slot at the end of the list. + wrenValueBufferWrite(vm, &list->elements, NULL_VAL); + + if (IS_OBJ(value)) wrenPopRoot(vm); + + // Shift the existing elements down. + for (uint32_t i = list->elements.count - 1; i > index; i--) { + list->elements.data[i] = list->elements.data[i - 1]; + } + + // Store the new element. + list->elements.data[index] = value; +} + +int wrenListIndexOf(WrenVM* vm, ObjList* list, Value value) { + int count = list->elements.count; + for (int i = 0; i < count; i++) { + Value item = list->elements.data[i]; + if (wrenValuesEqual(item, value)) { + return i; + } + } + return -1; +} + +Value wrenListRemoveAt(WrenVM* vm, ObjList* list, uint32_t index) { + Value removed = list->elements.data[index]; + + if (IS_OBJ(removed)) wrenPushRoot(vm, AS_OBJ(removed)); + + // Shift items up. + for (int i = index; i < list->elements.count - 1; i++) { + list->elements.data[i] = list->elements.data[i + 1]; + } + + // If we have too much excess capacity, shrink it. + if (list->elements.capacity / GROW_FACTOR >= list->elements.count) { + list->elements.data = (Value*)wrenReallocate(vm, list->elements.data, sizeof(Value) * list->elements.capacity, + sizeof(Value) * (list->elements.capacity / GROW_FACTOR)); + list->elements.capacity /= GROW_FACTOR; + } + + if (IS_OBJ(removed)) wrenPopRoot(vm); + + list->elements.count--; + return removed; +} + +ObjMap* wrenNewMap(WrenVM* vm) { + ObjMap* map = ALLOCATE(vm, ObjMap); + initObj(vm, &map->obj, OBJ_MAP, vm->mapClass); + map->capacity = 0; + map->count = 0; + map->entries = NULL; + return map; +} + +static inline uint32_t hashBits(uint64_t hash) { + // From v8's ComputeLongHash() which in turn cites: + // Thomas Wang, Integer Hash Functions. + // http://www.concentric.net/~Ttwang/tech/inthash.htm + hash = ~hash + (hash << 18); // hash = (hash << 18) - hash - 1; + hash = hash ^ (hash >> 31); + hash = hash * 21; // hash = (hash + (hash << 2)) + (hash << 4); + hash = hash ^ (hash >> 11); + hash = hash + (hash << 6); + hash = hash ^ (hash >> 22); + return (uint32_t)(hash & 0x3fffffff); +} + +// Generates a hash code for [num]. +static inline uint32_t hashNumber(double num) { + // Hash the raw bits of the value. + return hashBits(wrenDoubleToBits(num)); +} + +// Generates a hash code for [object]. +static uint32_t hashObject(Obj* object) { + switch (object->type) { + case OBJ_CLASS: + // Classes just use their name. + return hashObject((Obj*)((ObjClass*)object)->name); + + // Allow bare (non-closure) functions so that we can use a map to find + // existing constants in a function's constant table. This is only used + // internally. Since user code never sees a non-closure function, they + // cannot use them as map keys. + case OBJ_FN: { + ObjFn* fn = (ObjFn*)object; + return hashNumber(fn->arity) ^ hashNumber(fn->code.count); + } + + case OBJ_RANGE: { + ObjRange* range = (ObjRange*)object; + return hashNumber(range->from) ^ hashNumber(range->to); + } + + case OBJ_STRING: + return ((ObjString*)object)->hash; + + default: + ASSERT(false, "Only immutable objects can be hashed."); + return 0; + } +} + +// Generates a hash code for [value], which must be one of the built-in +// immutable types: null, bool, class, num, range, or string. +static uint32_t hashValue(Value value) { + // TODO: We'll probably want to randomize this at some point. + +#if WREN_NAN_TAGGING + if (IS_OBJ(value)) return hashObject(AS_OBJ(value)); + + // Hash the raw bits of the unboxed value. + return hashBits(value); +#else + switch (value.type) { + case VAL_FALSE: + return 0; + case VAL_NULL: + return 1; + case VAL_NUM: + return hashNumber(AS_NUM(value)); + case VAL_TRUE: + return 2; + case VAL_OBJ: + return hashObject(AS_OBJ(value)); + default: + UNREACHABLE(); + } + + return 0; +#endif +} + +// Looks for an entry with [key] in an array of [capacity] [entries]. +// +// If found, sets [result] to point to it and returns `true`. Otherwise, +// returns `false` and points [result] to the entry where the key/value pair +// should be inserted. +static bool findEntry(MapEntry* entries, uint32_t capacity, Value key, MapEntry** result) { + // If there is no entry array (an empty map), we definitely won't find it. + if (capacity == 0) return false; + + // Figure out where to insert it in the table. Use open addressing and + // basic linear probing. + uint32_t startIndex = hashValue(key) % capacity; + uint32_t index = startIndex; + + // If we pass a tombstone and don't end up finding the key, its entry will + // be re-used for the insert. + MapEntry* tombstone = NULL; + + // Walk the probe sequence until we've tried every slot. + do { + MapEntry* entry = &entries[index]; + + if (IS_UNDEFINED(entry->key)) { + // If we found an empty slot, the key is not in the table. If we found a + // slot that contains a deleted key, we have to keep looking. + if (IS_FALSE(entry->value)) { + // We found an empty slot, so we've reached the end of the probe + // sequence without finding the key. If we passed a tombstone, then + // that's where we should insert the item, otherwise, put it here at + // the end of the sequence. + *result = tombstone != NULL ? tombstone : entry; + return false; + } else { + // We found a tombstone. We need to keep looking in case the key is + // after it, but we'll use this entry as the insertion point if the + // key ends up not being found. + if (tombstone == NULL) tombstone = entry; + } + } else if (wrenValuesEqual(entry->key, key)) { + // We found the key. + *result = entry; + return true; + } + + // Try the next slot. + index = (index + 1) % capacity; + } while (index != startIndex); + + // If we get here, the table is full of tombstones. Return the first one we + // found. + ASSERT(tombstone != NULL, "Map should have tombstones or empty entries."); + *result = tombstone; + return false; +} + +// Inserts [key] and [value] in the array of [entries] with the given +// [capacity]. +// +// Returns `true` if this is the first time [key] was added to the map. +static bool insertEntry(MapEntry* entries, uint32_t capacity, Value key, Value value) { + ASSERT(entries != NULL, "Should ensure capacity before inserting."); + + MapEntry* entry; + if (findEntry(entries, capacity, key, &entry)) { + // Already present, so just replace the value. + entry->value = value; + return false; + } else { + entry->key = key; + entry->value = value; + return true; + } +} + +// Updates [map]'s entry array to [capacity]. +static void resizeMap(WrenVM* vm, ObjMap* map, uint32_t capacity) { + // Create the new empty hash table. + MapEntry* entries = ALLOCATE_ARRAY(vm, MapEntry, capacity); + for (uint32_t i = 0; i < capacity; i++) { + entries[i].key = UNDEFINED_VAL; + entries[i].value = FALSE_VAL; + } + + // Re-add the existing entries. + if (map->capacity > 0) { + for (uint32_t i = 0; i < map->capacity; i++) { + MapEntry* entry = &map->entries[i]; + + // Don't copy empty entries or tombstones. + if (IS_UNDEFINED(entry->key)) continue; + + insertEntry(entries, capacity, entry->key, entry->value); + } + } + + // Replace the array. + DEALLOCATE(vm, map->entries); + map->entries = entries; + map->capacity = capacity; +} + +Value wrenMapGet(ObjMap* map, Value key) { + MapEntry* entry; + if (findEntry(map->entries, map->capacity, key, &entry)) return entry->value; + + return UNDEFINED_VAL; +} + +void wrenMapSet(WrenVM* vm, ObjMap* map, Value key, Value value) { + // If the map is getting too full, make room first. + if (map->count + 1 > map->capacity * MAP_LOAD_PERCENT / 100) { + // Figure out the new hash table size. + uint32_t capacity = map->capacity * GROW_FACTOR; + if (capacity < MIN_CAPACITY) capacity = MIN_CAPACITY; + + resizeMap(vm, map, capacity); + } + + if (insertEntry(map->entries, map->capacity, key, value)) { + // A new key was added. + map->count++; + } +} + +void wrenMapClear(WrenVM* vm, ObjMap* map) { + DEALLOCATE(vm, map->entries); + map->entries = NULL; + map->capacity = 0; + map->count = 0; +} + +Value wrenMapRemoveKey(WrenVM* vm, ObjMap* map, Value key) { + MapEntry* entry; + if (!findEntry(map->entries, map->capacity, key, &entry)) return NULL_VAL; + + // Remove the entry from the map. Set this value to true, which marks it as a + // deleted slot. When searching for a key, we will stop on empty slots, but + // continue past deleted slots. + Value value = entry->value; + entry->key = UNDEFINED_VAL; + entry->value = TRUE_VAL; + + if (IS_OBJ(value)) wrenPushRoot(vm, AS_OBJ(value)); + + map->count--; + + if (map->count == 0) { + // Removed the last item, so free the array. + wrenMapClear(vm, map); + } else if (map->capacity > MIN_CAPACITY && map->count < map->capacity / GROW_FACTOR * MAP_LOAD_PERCENT / 100) { + uint32_t capacity = map->capacity / GROW_FACTOR; + if (capacity < MIN_CAPACITY) capacity = MIN_CAPACITY; + + // The map is getting empty, so shrink the entry array back down. + // TODO: Should we do this less aggressively than we grow? + resizeMap(vm, map, capacity); + } + + if (IS_OBJ(value)) wrenPopRoot(vm); + return value; +} + +ObjModule* wrenNewModule(WrenVM* vm, ObjString* name) { + ObjModule* module = ALLOCATE(vm, ObjModule); + + // Modules are never used as first-class objects, so don't need a class. + initObj(vm, (Obj*)module, OBJ_MODULE, NULL); + + wrenPushRoot(vm, (Obj*)module); + + wrenSymbolTableInit(&module->variableNames); + wrenValueBufferInit(&module->variables); + + module->name = name; + + wrenPopRoot(vm); + return module; +} + +Value wrenNewRange(WrenVM* vm, double from, double to, bool isInclusive) { + ObjRange* range = ALLOCATE(vm, ObjRange); + initObj(vm, &range->obj, OBJ_RANGE, vm->rangeClass); + range->from = from; + range->to = to; + range->isInclusive = isInclusive; + + return OBJ_VAL(range); +} + +// Creates a new string object with a null-terminated buffer large enough to +// hold a string of [length] but does not fill in the bytes. +// +// The caller is expected to fill in the buffer and then calculate the string's +// hash. +static ObjString* allocateString(WrenVM* vm, size_t length) { + ObjString* string = ALLOCATE_FLEX(vm, ObjString, char, length + 1); + initObj(vm, &string->obj, OBJ_STRING, vm->stringClass); + string->length = (int)length; + string->value[length] = '\0'; + + return string; +} + +// Calculates and stores the hash code for [string]. +static void hashString(ObjString* string) { + // FNV-1a hash. See: http://www.isthe.com/chongo/tech/comp/fnv/ + uint32_t hash = 2166136261u; + + // This is O(n) on the length of the string, but we only call this when a new + // string is created. Since the creation is also O(n) (to copy/initialize all + // the bytes), we allow this here. + for (uint32_t i = 0; i < string->length; i++) { + hash ^= string->value[i]; + hash *= 16777619; + } + + string->hash = hash; +} + +Value wrenNewString(WrenVM* vm, const char* text) { + return wrenNewStringLength(vm, text, strlen(text)); +} + +Value wrenNewStringLength(WrenVM* vm, const char* text, size_t length) { + // Allow NULL if the string is empty since byte buffers don't allocate any + // characters for a zero-length string. + ASSERT(length == 0 || text != NULL, "Unexpected NULL string."); + + ObjString* string = allocateString(vm, length); + + // Copy the string (if given one). + if (length > 0 && text != NULL) memcpy(string->value, text, length); + + hashString(string); + return OBJ_VAL(string); +} + +Value wrenNewStringFromRange(WrenVM* vm, ObjString* source, int start, uint32_t count, int step) { + uint8_t* from = (uint8_t*)source->value; + int length = 0; + for (uint32_t i = 0; i < count; i++) { + length += wrenUtf8DecodeNumBytes(from[start + i * step]); + } + + ObjString* result = allocateString(vm, length); + result->value[length] = '\0'; + + uint8_t* to = (uint8_t*)result->value; + for (uint32_t i = 0; i < count; i++) { + int index = start + i * step; + int codePoint = wrenUtf8Decode(from + index, source->length - index); + + if (codePoint != -1) { + to += wrenUtf8Encode(codePoint, to); + } + } + + hashString(result); + return OBJ_VAL(result); +} + +Value wrenNumToString(WrenVM* vm, double value) { + // Edge case: If the value is NaN or infinity, different versions of libc + // produce different outputs (some will format it signed and some won't). To + // get reliable output, handle it ourselves. + if (isnan(value)) return CONST_STRING(vm, "nan"); + if (isinf(value)) { + if (value > 0.0) { + return CONST_STRING(vm, "infinity"); + } else { + return CONST_STRING(vm, "-infinity"); + } + } + + // This is large enough to hold any double converted to a string using + // "%.14g". Example: + // + // -1.12345678901234e-1022 + // + // So we have: + // + // + 1 char for sign + // + 1 char for digit + // + 1 char for "." + // + 14 chars for decimal digits + // + 1 char for "e" + // + 1 char for "-" or "+" + // + 4 chars for exponent + // + 1 char for "\0" + // = 24 + char buffer[24]; + int length = sprintf(buffer, "%.14g", value); + return wrenNewStringLength(vm, buffer, length); +} + +Value wrenStringFromCodePoint(WrenVM* vm, int value) { + int length = wrenUtf8EncodeNumBytes(value); + ASSERT(length != 0, "Value out of range."); + + ObjString* string = allocateString(vm, length); + + wrenUtf8Encode(value, (uint8_t*)string->value); + hashString(string); + + return OBJ_VAL(string); +} + +Value wrenStringFromByte(WrenVM* vm, uint8_t value) { + int length = 1; + ObjString* string = allocateString(vm, length); + string->value[0] = value; + hashString(string); + return OBJ_VAL(string); +} + +Value wrenStringFormat(WrenVM* vm, const char* format, ...) { + va_list argList; + + // Calculate the length of the result string. Do this up front so we can + // create the final string with a single allocation. + va_start(argList, format); + size_t totalLength = 0; + for (const char* c = format; *c != '\0'; c++) { + switch (*c) { + case '$': + totalLength += strlen(va_arg(argList, const char*)); + break; + + case '@': + totalLength += AS_STRING(va_arg(argList, Value))->length; + break; + + default: + // Any other character is interpreted literally. + totalLength++; + } + } + va_end(argList); + + // Concatenate the string. + ObjString* result = allocateString(vm, totalLength); + + va_start(argList, format); + char* start = result->value; + for (const char* c = format; *c != '\0'; c++) { + switch (*c) { + case '$': { + const char* string = va_arg(argList, const char*); + size_t length = strlen(string); + memcpy(start, string, length); + start += length; + break; + } + + case '@': { + ObjString* string = AS_STRING(va_arg(argList, Value)); + memcpy(start, string->value, string->length); + start += string->length; + break; + } + + default: + // Any other character is interpreted literally. + *start++ = *c; + } + } + va_end(argList); + + hashString(result); + + return OBJ_VAL(result); +} + +Value wrenStringCodePointAt(WrenVM* vm, ObjString* string, uint32_t index) { + ASSERT(index < string->length, "Index out of bounds."); + + int codePoint = wrenUtf8Decode((uint8_t*)string->value + index, string->length - index); + if (codePoint == -1) { + // If it isn't a valid UTF-8 sequence, treat it as a single raw byte. + char bytes[2]; + bytes[0] = string->value[index]; + bytes[1] = '\0'; + return wrenNewStringLength(vm, bytes, 1); + } + + return wrenStringFromCodePoint(vm, codePoint); +} + +// Uses the Boyer-Moore-Horspool string matching algorithm. +uint32_t wrenStringFind(ObjString* haystack, ObjString* needle, uint32_t start) { + // Edge case: An empty needle is always found. + if (needle->length == 0) return start; + + // If the needle goes past the haystack it won't be found. + if (start + needle->length > haystack->length) return UINT32_MAX; + + // If the startIndex is too far it also won't be found. + if (start >= haystack->length) return UINT32_MAX; + + // Pre-calculate the shift table. For each character (8-bit value), we + // determine how far the search window can be advanced if that character is + // the last character in the haystack where we are searching for the needle + // and the needle doesn't match there. + uint32_t shift[UINT8_MAX]; + uint32_t needleEnd = needle->length - 1; + + // By default, we assume the character is not the needle at all. In that case + // case, if a match fails on that character, we can advance one whole needle + // width since. + for (uint32_t index = 0; index < UINT8_MAX; index++) { + shift[index] = needle->length; + } + + // Then, for every character in the needle, determine how far it is from the + // end. If a match fails on that character, we can advance the window such + // that it the last character in it lines up with the last place we could + // find it in the needle. + for (uint32_t index = 0; index < needleEnd; index++) { + char c = needle->value[index]; + shift[(uint8_t)c] = needleEnd - index; + } + + // Slide the needle across the haystack, looking for the first match or + // stopping if the needle goes off the end. + char lastChar = needle->value[needleEnd]; + uint32_t range = haystack->length - needle->length; + + for (uint32_t index = start; index <= range;) { + // Compare the last character in the haystack's window to the last character + // in the needle. If it matches, see if the whole needle matches. + char c = haystack->value[index + needleEnd]; + if (lastChar == c && memcmp(haystack->value + index, needle->value, needleEnd) == 0) { + // Found a match. + return index; + } + + // Otherwise, slide the needle forward. + index += shift[(uint8_t)c]; + } + + // Not found. + return UINT32_MAX; +} + +ObjUpvalue* wrenNewUpvalue(WrenVM* vm, Value* value) { + ObjUpvalue* upvalue = ALLOCATE(vm, ObjUpvalue); + + // Upvalues are never used as first-class objects, so don't need a class. + initObj(vm, &upvalue->obj, OBJ_UPVALUE, NULL); + + upvalue->value = value; + upvalue->closed = NULL_VAL; + upvalue->next = NULL; + return upvalue; +} + +void wrenGrayObj(WrenVM* vm, Obj* obj) { + if (obj == NULL) return; + + // Stop if the object is already darkened so we don't get stuck in a cycle. + if (obj->isDark) return; + + // It's been reached. + obj->isDark = true; + + // Add it to the gray list so it can be recursively explored for + // more marks later. + if (vm->grayCount >= vm->grayCapacity) { + vm->grayCapacity = vm->grayCount * 2; + vm->gray = (Obj**)vm->config.reallocateFn(vm->gray, vm->grayCapacity * sizeof(Obj*), vm->config.userData); + } + + vm->gray[vm->grayCount++] = obj; +} + +void wrenGrayValue(WrenVM* vm, Value value) { + if (!IS_OBJ(value)) return; + wrenGrayObj(vm, AS_OBJ(value)); +} + +void wrenGrayBuffer(WrenVM* vm, ValueBuffer* buffer) { + for (int i = 0; i < buffer->count; i++) { + wrenGrayValue(vm, buffer->data[i]); + } +} + +static void blackenClass(WrenVM* vm, ObjClass* classObj) { + // The metaclass. + wrenGrayObj(vm, (Obj*)classObj->obj.classObj); + + // The superclass. + wrenGrayObj(vm, (Obj*)classObj->superclass); + + // Method function objects. + for (int i = 0; i < classObj->methods.count; i++) { + if (classObj->methods.data[i].type == METHOD_BLOCK) { + wrenGrayObj(vm, (Obj*)classObj->methods.data[i].as.closure); + } + } + + wrenGrayObj(vm, (Obj*)classObj->name); + + if (!IS_NULL(classObj->attributes)) wrenGrayObj(vm, AS_OBJ(classObj->attributes)); + + // Keep track of how much memory is still in use. + vm->bytesAllocated += sizeof(ObjClass); + vm->bytesAllocated += classObj->methods.capacity * sizeof(Method); +} + +static void blackenClosure(WrenVM* vm, ObjClosure* closure) { + // Mark the function. + wrenGrayObj(vm, (Obj*)closure->fn); + + // Mark the upvalues. + for (int i = 0; i < closure->fn->numUpvalues; i++) { + wrenGrayObj(vm, (Obj*)closure->upvalues[i]); + } + + // Keep track of how much memory is still in use. + vm->bytesAllocated += sizeof(ObjClosure); + vm->bytesAllocated += sizeof(ObjUpvalue*) * closure->fn->numUpvalues; +} + +static void blackenFiber(WrenVM* vm, ObjFiber* fiber) { + // Stack functions. + for (int i = 0; i < fiber->numFrames; i++) { + wrenGrayObj(vm, (Obj*)fiber->frames[i].closure); + } + + // Stack variables. + for (Value* slot = fiber->stack; slot < fiber->stackTop; slot++) { + wrenGrayValue(vm, *slot); + } + + // Open upvalues. + ObjUpvalue* upvalue = fiber->openUpvalues; + while (upvalue != NULL) { + wrenGrayObj(vm, (Obj*)upvalue); + upvalue = upvalue->next; + } + + // The caller. + wrenGrayObj(vm, (Obj*)fiber->caller); + wrenGrayValue(vm, fiber->error); + + // Keep track of how much memory is still in use. + vm->bytesAllocated += sizeof(ObjFiber); + vm->bytesAllocated += fiber->frameCapacity * sizeof(CallFrame); + vm->bytesAllocated += fiber->stackCapacity * sizeof(Value); +} + +static void blackenFn(WrenVM* vm, ObjFn* fn) { + // Mark the constants. + wrenGrayBuffer(vm, &fn->constants); + + // Keep track of how much memory is still in use. + vm->bytesAllocated += sizeof(ObjFn); + vm->bytesAllocated += sizeof(uint8_t) * fn->code.capacity; + vm->bytesAllocated += sizeof(Value) * fn->constants.capacity; + + // The debug line number buffer. + vm->bytesAllocated += sizeof(int) * fn->code.capacity; + // TODO: What about the function name? +} + +static void blackenForeign(WrenVM* vm, ObjForeign* foreign) { + // TODO: Keep track of how much memory the foreign object uses. We can store + // this in each foreign object, but it will balloon the size. We may not want + // that much overhead. One option would be to let the foreign class register + // a C function that returns a size for the object. That way the VM doesn't + // always have to explicitly store it. +} + +static void blackenInstance(WrenVM* vm, ObjInstance* instance) { + wrenGrayObj(vm, (Obj*)instance->obj.classObj); + + // Mark the fields. + for (int i = 0; i < instance->obj.classObj->numFields; i++) { + wrenGrayValue(vm, instance->fields[i]); + } + + // Keep track of how much memory is still in use. + vm->bytesAllocated += sizeof(ObjInstance); + vm->bytesAllocated += sizeof(Value) * instance->obj.classObj->numFields; +} + +static void blackenList(WrenVM* vm, ObjList* list) { + // Mark the elements. + wrenGrayBuffer(vm, &list->elements); + + // Keep track of how much memory is still in use. + vm->bytesAllocated += sizeof(ObjList); + vm->bytesAllocated += sizeof(Value) * list->elements.capacity; +} + +static void blackenMap(WrenVM* vm, ObjMap* map) { + // Mark the entries. + for (uint32_t i = 0; i < map->capacity; i++) { + MapEntry* entry = &map->entries[i]; + if (IS_UNDEFINED(entry->key)) continue; + + wrenGrayValue(vm, entry->key); + wrenGrayValue(vm, entry->value); + } + + // Keep track of how much memory is still in use. + vm->bytesAllocated += sizeof(ObjMap); + vm->bytesAllocated += sizeof(MapEntry) * map->capacity; +} + +static void blackenModule(WrenVM* vm, ObjModule* module) { + // Top-level variables. + for (int i = 0; i < module->variables.count; i++) { + wrenGrayValue(vm, module->variables.data[i]); + } + + wrenBlackenSymbolTable(vm, &module->variableNames); + + wrenGrayObj(vm, (Obj*)module->name); + + // Keep track of how much memory is still in use. + vm->bytesAllocated += sizeof(ObjModule); +} + +static void blackenRange(WrenVM* vm, ObjRange* range) { + // Keep track of how much memory is still in use. + vm->bytesAllocated += sizeof(ObjRange); +} + +static void blackenString(WrenVM* vm, ObjString* string) { + // Keep track of how much memory is still in use. + vm->bytesAllocated += sizeof(ObjString) + string->length + 1; +} + +static void blackenUpvalue(WrenVM* vm, ObjUpvalue* upvalue) { + // Mark the closed-over object (in case it is closed). + wrenGrayValue(vm, upvalue->closed); + + // Keep track of how much memory is still in use. + vm->bytesAllocated += sizeof(ObjUpvalue); +} + +static void blackenObject(WrenVM* vm, Obj* obj) { +#if WREN_DEBUG_TRACE_MEMORY + printf("mark "); + wrenDumpValue(OBJ_VAL(obj)); + printf(" @ %p\n", obj); +#endif + + // Traverse the object's fields. + switch (obj->type) { + case OBJ_CLASS: + blackenClass(vm, (ObjClass*)obj); + break; + case OBJ_CLOSURE: + blackenClosure(vm, (ObjClosure*)obj); + break; + case OBJ_FIBER: + blackenFiber(vm, (ObjFiber*)obj); + break; + case OBJ_FN: + blackenFn(vm, (ObjFn*)obj); + break; + case OBJ_FOREIGN: + blackenForeign(vm, (ObjForeign*)obj); + break; + case OBJ_INSTANCE: + blackenInstance(vm, (ObjInstance*)obj); + break; + case OBJ_LIST: + blackenList(vm, (ObjList*)obj); + break; + case OBJ_MAP: + blackenMap(vm, (ObjMap*)obj); + break; + case OBJ_MODULE: + blackenModule(vm, (ObjModule*)obj); + break; + case OBJ_RANGE: + blackenRange(vm, (ObjRange*)obj); + break; + case OBJ_STRING: + blackenString(vm, (ObjString*)obj); + break; + case OBJ_UPVALUE: + blackenUpvalue(vm, (ObjUpvalue*)obj); + break; + } +} + +void wrenBlackenObjects(WrenVM* vm) { + while (vm->grayCount > 0) { + // Pop an item from the gray stack. + Obj* obj = vm->gray[--vm->grayCount]; + blackenObject(vm, obj); + } +} + +void wrenFreeObj(WrenVM* vm, Obj* obj) { +#if WREN_DEBUG_TRACE_MEMORY + printf("free "); + wrenDumpValue(OBJ_VAL(obj)); + printf(" @ %p\n", obj); +#endif + + switch (obj->type) { + case OBJ_CLASS: + wrenMethodBufferClear(vm, &((ObjClass*)obj)->methods); + break; + + case OBJ_FIBER: { + ObjFiber* fiber = (ObjFiber*)obj; + DEALLOCATE(vm, fiber->frames); + DEALLOCATE(vm, fiber->stack); + break; + } + + case OBJ_FN: { + ObjFn* fn = (ObjFn*)obj; + wrenValueBufferClear(vm, &fn->constants); + wrenByteBufferClear(vm, &fn->code); + wrenIntBufferClear(vm, &fn->debug->sourceLines); + DEALLOCATE(vm, fn->debug->name); + DEALLOCATE(vm, fn->debug); + break; + } + + case OBJ_FOREIGN: + wrenFinalizeForeign(vm, (ObjForeign*)obj); + break; + + case OBJ_LIST: + wrenValueBufferClear(vm, &((ObjList*)obj)->elements); + break; + + case OBJ_MAP: + DEALLOCATE(vm, ((ObjMap*)obj)->entries); + break; + + case OBJ_MODULE: + wrenSymbolTableClear(vm, &((ObjModule*)obj)->variableNames); + wrenValueBufferClear(vm, &((ObjModule*)obj)->variables); + break; + + case OBJ_CLOSURE: + case OBJ_INSTANCE: + case OBJ_RANGE: + case OBJ_STRING: + case OBJ_UPVALUE: + break; + } + + DEALLOCATE(vm, obj); +} + +ObjClass* wrenGetClass(WrenVM* vm, Value value) { + return wrenGetClassInline(vm, value); +} + +bool wrenValuesEqual(Value a, Value b) { + if (wrenValuesSame(a, b)) return true; + + // If we get here, it's only possible for two heap-allocated immutable objects + // to be equal. + if (!IS_OBJ(a) || !IS_OBJ(b)) return false; + + Obj* aObj = AS_OBJ(a); + Obj* bObj = AS_OBJ(b); + + // Must be the same type. + if (aObj->type != bObj->type) return false; + + switch (aObj->type) { + case OBJ_RANGE: { + ObjRange* aRange = (ObjRange*)aObj; + ObjRange* bRange = (ObjRange*)bObj; + return aRange->from == bRange->from && aRange->to == bRange->to && aRange->isInclusive == bRange->isInclusive; + } + + case OBJ_STRING: { + ObjString* aString = (ObjString*)aObj; + ObjString* bString = (ObjString*)bObj; + return aString->hash == bString->hash && wrenStringEqualsCString(aString, bString->value, bString->length); + } + + default: + // All other types are only equal if they are same, which they aren't if + // we get here. + return false; + } +} +// End file "wren_value.c" +// Begin file "wren_utils.c" +#include + +DEFINE_BUFFER(Byte, uint8_t); +DEFINE_BUFFER(Int, int); +DEFINE_BUFFER(String, ObjString*); + +void wrenSymbolTableInit(SymbolTable* symbols) { + wrenStringBufferInit(symbols); +} + +void wrenSymbolTableClear(WrenVM* vm, SymbolTable* symbols) { + wrenStringBufferClear(vm, symbols); +} + +int wrenSymbolTableAdd(WrenVM* vm, SymbolTable* symbols, const char* name, size_t length) { + ObjString* symbol = AS_STRING(wrenNewStringLength(vm, name, length)); + + wrenPushRoot(vm, &symbol->obj); + wrenStringBufferWrite(vm, symbols, symbol); + wrenPopRoot(vm); + + return symbols->count - 1; +} + +int wrenSymbolTableEnsure(WrenVM* vm, SymbolTable* symbols, const char* name, size_t length) { + // See if the symbol is already defined. + int existing = wrenSymbolTableFind(symbols, name, length); + if (existing != -1) return existing; + + // New symbol, so add it. + return wrenSymbolTableAdd(vm, symbols, name, length); +} + +int wrenSymbolTableFind(const SymbolTable* symbols, const char* name, size_t length) { + // See if the symbol is already defined. + // TODO: O(n). Do something better. + for (int i = 0; i < symbols->count; i++) { + if (wrenStringEqualsCString(symbols->data[i], name, length)) return i; + } + + return -1; +} + +void wrenBlackenSymbolTable(WrenVM* vm, SymbolTable* symbolTable) { + for (int i = 0; i < symbolTable->count; i++) { + wrenGrayObj(vm, &symbolTable->data[i]->obj); + } + + // Keep track of how much memory is still in use. + vm->bytesAllocated += symbolTable->capacity * sizeof(*symbolTable->data); +} + +int wrenUtf8EncodeNumBytes(int value) { + ASSERT(value >= 0, "Cannot encode a negative value."); + + if (value <= 0x7f) return 1; + if (value <= 0x7ff) return 2; + if (value <= 0xffff) return 3; + if (value <= 0x10ffff) return 4; + return 0; +} + +int wrenUtf8Encode(int value, uint8_t* bytes) { + if (value <= 0x7f) { + // Single byte (i.e. fits in ASCII). + *bytes = value & 0x7f; + return 1; + } else if (value <= 0x7ff) { + // Two byte sequence: 110xxxxx 10xxxxxx. + *bytes = 0xc0 | ((value & 0x7c0) >> 6); + bytes++; + *bytes = 0x80 | (value & 0x3f); + return 2; + } else if (value <= 0xffff) { + // Three byte sequence: 1110xxxx 10xxxxxx 10xxxxxx. + *bytes = 0xe0 | ((value & 0xf000) >> 12); + bytes++; + *bytes = 0x80 | ((value & 0xfc0) >> 6); + bytes++; + *bytes = 0x80 | (value & 0x3f); + return 3; + } else if (value <= 0x10ffff) { + // Four byte sequence: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx. + *bytes = 0xf0 | ((value & 0x1c0000) >> 18); + bytes++; + *bytes = 0x80 | ((value & 0x3f000) >> 12); + bytes++; + *bytes = 0x80 | ((value & 0xfc0) >> 6); + bytes++; + *bytes = 0x80 | (value & 0x3f); + return 4; + } + + // Invalid Unicode value. See: http://tools.ietf.org/html/rfc3629 + UNREACHABLE(); + return 0; +} + +int wrenUtf8Decode(const uint8_t* bytes, uint32_t length) { + // Single byte (i.e. fits in ASCII). + if (*bytes <= 0x7f) return *bytes; + + int value; + uint32_t remainingBytes; + if ((*bytes & 0xe0) == 0xc0) { + // Two byte sequence: 110xxxxx 10xxxxxx. + value = *bytes & 0x1f; + remainingBytes = 1; + } else if ((*bytes & 0xf0) == 0xe0) { + // Three byte sequence: 1110xxxx 10xxxxxx 10xxxxxx. + value = *bytes & 0x0f; + remainingBytes = 2; + } else if ((*bytes & 0xf8) == 0xf0) { + // Four byte sequence: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx. + value = *bytes & 0x07; + remainingBytes = 3; + } else { + // Invalid UTF-8 sequence. + return -1; + } + + // Don't read past the end of the buffer on truncated UTF-8. + if (remainingBytes > length - 1) return -1; + + while (remainingBytes > 0) { + bytes++; + remainingBytes--; + + // Remaining bytes must be of form 10xxxxxx. + if ((*bytes & 0xc0) != 0x80) return -1; + + value = value << 6 | (*bytes & 0x3f); + } + + return value; +} + +int wrenUtf8DecodeNumBytes(uint8_t byte) { + // If the byte starts with 10xxxxx, it's the middle of a UTF-8 sequence, so + // don't count it at all. + if ((byte & 0xc0) == 0x80) return 0; + + // The first byte's high bits tell us how many bytes are in the UTF-8 + // sequence. + if ((byte & 0xf8) == 0xf0) return 4; + if ((byte & 0xf0) == 0xe0) return 3; + if ((byte & 0xe0) == 0xc0) return 2; + return 1; +} + +// From: http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2Float +int wrenPowerOf2Ceil(int n) { + n--; + n |= n >> 1; + n |= n >> 2; + n |= n >> 4; + n |= n >> 8; + n |= n >> 16; + n++; + + return n; +} + +uint32_t wrenValidateIndex(uint32_t count, int64_t value) { + // Negative indices count from the end. + if (value < 0) value = count + value; + + // Check bounds. + if (value >= 0 && value < count) return (uint32_t)value; + + return UINT32_MAX; +} +// End file "wren_utils.c" +// Begin file "wren_compiler.c" +#include +#include +#include +#include + +#if WREN_DEBUG_DUMP_COMPILED_CODE +#endif + +// This is written in bottom-up order, so the tokenization comes first, then +// parsing/code generation. This minimizes the number of explicit forward +// declarations needed. + +// The maximum number of local (i.e. not module level) variables that can be +// declared in a single function, method, or chunk of top level code. This is +// the maximum number of variables in scope at one time, and spans block scopes. +// +// Note that this limitation is also explicit in the bytecode. Since +// `CODE_LOAD_LOCAL` and `CODE_STORE_LOCAL` use a single argument byte to +// identify the local, only 256 can be in scope at one time. +#define MAX_LOCALS 256 + +// The maximum number of upvalues (i.e. variables from enclosing functions) +// that a function can close over. +#define MAX_UPVALUES 256 + +// The maximum number of distinct constants that a function can contain. This +// value is explicit in the bytecode since `CODE_CONSTANT` only takes a single +// two-byte argument. +#define MAX_CONSTANTS (1 << 16) + +// The maximum distance a CODE_JUMP or CODE_JUMP_IF instruction can move the +// instruction pointer. +#define MAX_JUMP (1 << 16) + +// The maximum depth that interpolation can nest. For example, this string has +// three levels: +// +// "outside %(one + "%(two + "%(three)")")" +#define MAX_INTERPOLATION_NESTING 8 + +// The buffer size used to format a compile error message, excluding the header +// with the module name and error location. Using a hardcoded buffer for this +// is kind of hairy, but fortunately we can control what the longest possible +// message is and handle that. Ideally, we'd use `snprintf()`, but that's not +// available in standard C++98. +#define ERROR_MESSAGE_SIZE (80 + MAX_VARIABLE_NAME + 15) + +typedef enum { + TOKEN_LEFT_PAREN, + TOKEN_RIGHT_PAREN, + TOKEN_LEFT_BRACKET, + TOKEN_RIGHT_BRACKET, + TOKEN_LEFT_BRACE, + TOKEN_RIGHT_BRACE, + TOKEN_COLON, + TOKEN_DOT, + TOKEN_DOTDOT, + TOKEN_DOTDOTDOT, + TOKEN_COMMA, + TOKEN_STAR, + TOKEN_SLASH, + TOKEN_PERCENT, + TOKEN_HASH, + TOKEN_PLUS, + TOKEN_MINUS, + TOKEN_LTLT, + TOKEN_GTGT, + TOKEN_PIPE, + TOKEN_PIPEPIPE, + TOKEN_CARET, + TOKEN_AMP, + TOKEN_AMPAMP, + TOKEN_BANG, + TOKEN_TILDE, + TOKEN_QUESTION, + TOKEN_EQ, + TOKEN_LT, + TOKEN_GT, + TOKEN_LTEQ, + TOKEN_GTEQ, + TOKEN_EQEQ, + TOKEN_BANGEQ, + + TOKEN_BREAK, + TOKEN_CONTINUE, + TOKEN_CLASS, + TOKEN_CONSTRUCT, + TOKEN_ELSE, + TOKEN_FALSE, + TOKEN_FOR, + TOKEN_FOREIGN, + TOKEN_IF, + TOKEN_IMPORT, + TOKEN_AS, + TOKEN_IN, + TOKEN_IS, + TOKEN_NULL, + TOKEN_RETURN, + TOKEN_STATIC, + TOKEN_SUPER, + TOKEN_THIS, + TOKEN_TRUE, + TOKEN_VAR, + TOKEN_WHILE, + + TOKEN_FIELD, + TOKEN_STATIC_FIELD, + TOKEN_NAME, + TOKEN_NUMBER, + + // A string literal without any interpolation, or the last section of a + // string following the last interpolated expression. + TOKEN_STRING, + + // A portion of a string literal preceding an interpolated expression. This + // string: + // + // "a %(b) c %(d) e" + // + // is tokenized to: + // + // TOKEN_INTERPOLATION "a " + // TOKEN_NAME b + // TOKEN_INTERPOLATION " c " + // TOKEN_NAME d + // TOKEN_STRING " e" + TOKEN_INTERPOLATION, + + TOKEN_LINE, + + TOKEN_ERROR, + TOKEN_EOF +} TokenType; + +typedef struct { + TokenType type; + + // The beginning of the token, pointing directly into the source. + const char* start; + + // The length of the token in characters. + int length; + + // The 1-based line where the token appears. + int line; + + // The parsed value if the token is a literal. + Value value; +} Token; + +typedef struct { + WrenVM* vm; + + // The module being parsed. + ObjModule* module; + + // The source code being parsed. + const char* source; + + // The beginning of the currently-being-lexed token in [source]. + const char* tokenStart; + + // The current character being lexed in [source]. + const char* currentChar; + + // The 1-based line number of [currentChar]. + int currentLine; + + // The upcoming token. + Token next; + + // The most recently lexed token. + Token current; + + // The most recently consumed/advanced token. + Token previous; + + // Tracks the lexing state when tokenizing interpolated strings. + // + // Interpolated strings make the lexer not strictly regular: we don't know + // whether a ")" should be treated as a RIGHT_PAREN token or as ending an + // interpolated expression unless we know whether we are inside a string + // interpolation and how many unmatched "(" there are. This is particularly + // complex because interpolation can nest: + // + // " %( " %( inner ) " ) " + // + // This tracks that state. The parser maintains a stack of ints, one for each + // level of current interpolation nesting. Each value is the number of + // unmatched "(" that are waiting to be closed. + int parens[MAX_INTERPOLATION_NESTING]; + int numParens; + + // Whether compile errors should be printed to stderr or discarded. + bool printErrors; + + // If a syntax or compile error has occurred. + bool hasError; +} Parser; + +typedef struct { + // The name of the local variable. This points directly into the original + // source code string. + const char* name; + + // The length of the local variable's name. + int length; + + // The depth in the scope chain that this variable was declared at. Zero is + // the outermost scope--parameters for a method, or the first local block in + // top level code. One is the scope within that, etc. + int depth; + + // If this local variable is being used as an upvalue. + bool isUpvalue; +} Local; + +typedef struct { + // True if this upvalue is capturing a local variable from the enclosing + // function. False if it's capturing an upvalue. + bool isLocal; + + // The index of the local or upvalue being captured in the enclosing function. + int index; +} CompilerUpvalue; + +// Bookkeeping information for the current loop being compiled. +typedef struct sLoop { + // Index of the instruction that the loop should jump back to. + int start; + + // Index of the argument for the CODE_JUMP_IF instruction used to exit the + // loop. Stored so we can patch it once we know where the loop ends. + int exitJump; + + // Index of the first instruction of the body of the loop. + int body; + + // Depth of the scope(s) that need to be exited if a break is hit inside the + // loop. + int scopeDepth; + + // The loop enclosing this one, or NULL if this is the outermost loop. + struct sLoop* enclosing; +} Loop; + +// The different signature syntaxes for different kinds of methods. +typedef enum { + // A name followed by a (possibly empty) parenthesized parameter list. Also + // used for binary operators. + SIG_METHOD, + + // Just a name. Also used for unary operators. + SIG_GETTER, + + // A name followed by "=". + SIG_SETTER, + + // A square bracketed parameter list. + SIG_SUBSCRIPT, + + // A square bracketed parameter list followed by "=". + SIG_SUBSCRIPT_SETTER, + + // A constructor initializer function. This has a distinct signature to + // prevent it from being invoked directly outside of the constructor on the + // metaclass. + SIG_INITIALIZER +} SignatureType; + +typedef struct { + const char* name; + int length; + SignatureType type; + int arity; +} Signature; + +// Bookkeeping information for compiling a class definition. +typedef struct { + // The name of the class. + ObjString* name; + + // Attributes for the class itself + ObjMap* classAttributes; + // Attributes for methods in this class + ObjMap* methodAttributes; + + // Symbol table for the fields of the class. + SymbolTable fields; + + // Symbols for the methods defined by the class. Used to detect duplicate + // method definitions. + IntBuffer methods; + IntBuffer staticMethods; + + // True if the class being compiled is a foreign class. + bool isForeign; + + // True if the current method being compiled is static. + bool inStatic; + + // The signature of the method being compiled. + Signature* signature; +} ClassInfo; + +struct sCompiler { + Parser* parser; + + // The compiler for the function enclosing this one, or NULL if it's the + // top level. + struct sCompiler* parent; + + // The currently in scope local variables. + Local locals[MAX_LOCALS]; + + // The number of local variables currently in scope. + int numLocals; + + // The upvalues that this function has captured from outer scopes. The count + // of them is stored in [numUpvalues]. + CompilerUpvalue upvalues[MAX_UPVALUES]; + + // The current level of block scope nesting, where zero is no nesting. A -1 + // here means top-level code is being compiled and there is no block scope + // in effect at all. Any variables declared will be module-level. + int scopeDepth; + + // The current number of slots (locals and temporaries) in use. + // + // We use this and maxSlots to track the maximum number of additional slots + // a function may need while executing. When the function is called, the + // fiber will check to ensure its stack has enough room to cover that worst + // case and grow the stack if needed. + // + // This value here doesn't include parameters to the function. Since those + // are already pushed onto the stack by the caller and tracked there, we + // don't need to double count them here. + int numSlots; + + // The current innermost loop being compiled, or NULL if not in a loop. + Loop* loop; + + // If this is a compiler for a method, keeps track of the class enclosing it. + ClassInfo* enclosingClass; + + // The function being compiled. + ObjFn* fn; + + // The constants for the function being compiled. + ObjMap* constants; + + // Whether or not the compiler is for a constructor initializer + bool isInitializer; + + // The number of attributes seen while parsing. + // We track this separately as compile time attributes + // are not stored, so we can't rely on attributes->count + // to enforce an error message when attributes are used + // anywhere other than methods or classes. + int numAttributes; + // Attributes for the next class or method. + ObjMap* attributes; +}; + +// Describes where a variable is declared. +typedef enum { + // A local variable in the current function. + SCOPE_LOCAL, + + // A local variable declared in an enclosing function. + SCOPE_UPVALUE, + + // A top-level module variable. + SCOPE_MODULE +} Scope; + +// A reference to a variable and the scope where it is defined. This contains +// enough information to emit correct code to load or store the variable. +typedef struct { + // The stack slot, upvalue slot, or module symbol defining the variable. + int index; + + // Where the variable is declared. + Scope scope; +} Variable; + +// Forward declarations +static void disallowAttributes(Compiler* compiler); +static void addToAttributeGroup(Compiler* compiler, Value group, Value key, Value value); +static void emitClassAttributes(Compiler* compiler, ClassInfo* classInfo); +static void copyAttributes(Compiler* compiler, ObjMap* into); +static void copyMethodAttributes(Compiler* compiler, bool isForeign, bool isStatic, const char* fullSignature, + int32_t length); + +// The stack effect of each opcode. The index in the array is the opcode, and +// the value is the stack effect of that instruction. +static const int stackEffects[] = { +#define OPCODE(_, effect) effect, + // Begin file "wren_opcodes.h" + // This defines the bytecode instructions used by the VM. It does so by invoking + // an OPCODE() macro which is expected to be defined at the point that this is + // included. (See: http://en.wikipedia.org/wiki/X_Macro for more.) + // + // The first argument is the name of the opcode. The second is its "stack + // effect" -- the amount that the op code changes the size of the stack. A + // stack effect of 1 means it pushes a value and the stack grows one larger. + // -2 means it pops two values, etc. + // + // Note that the order of instructions here affects the order of the dispatch + // table in the VM's interpreter loop. That in turn affects caching which + // affects overall performance. Take care to run benchmarks if you change the + // order here. + + // Load the constant at index [arg]. + OPCODE(CONSTANT, 1) + + // Push null onto the stack. + OPCODE(NULL, 1) + + // Push false onto the stack. + OPCODE(FALSE, 1) + + // Push true onto the stack. + OPCODE(TRUE, 1) + + // Pushes the value in the given local slot. + OPCODE(LOAD_LOCAL_0, 1) OPCODE(LOAD_LOCAL_1, 1) OPCODE(LOAD_LOCAL_2, 1) OPCODE(LOAD_LOCAL_3, 1) + OPCODE(LOAD_LOCAL_4, 1) OPCODE(LOAD_LOCAL_5, 1) OPCODE(LOAD_LOCAL_6, 1) OPCODE(LOAD_LOCAL_7, 1) + OPCODE(LOAD_LOCAL_8, 1) + + // Note: The compiler assumes the following _STORE instructions always + // immediately follow their corresponding _LOAD ones. + + // Pushes the value in local slot [arg]. + OPCODE(LOAD_LOCAL, 1) + + // Stores the top of stack in local slot [arg]. Does not pop it. + OPCODE(STORE_LOCAL, 0) + + // Pushes the value in upvalue [arg]. + OPCODE(LOAD_UPVALUE, 1) + + // Stores the top of stack in upvalue [arg]. Does not pop it. + OPCODE(STORE_UPVALUE, 0) + + // Pushes the value of the top-level variable in slot [arg]. + OPCODE(LOAD_MODULE_VAR, 1) + + // Stores the top of stack in top-level variable slot [arg]. Does not pop it. + OPCODE(STORE_MODULE_VAR, 0) + + // Pushes the value of the field in slot [arg] of the receiver of the current + // function. This is used for regular field accesses on "this" directly in + // methods. This instruction is faster than the more general CODE_LOAD_FIELD + // instruction. + OPCODE(LOAD_FIELD_THIS, 1) + + // Stores the top of the stack in field slot [arg] in the receiver of the + // current value. Does not pop the value. This instruction is faster than the + // more general CODE_LOAD_FIELD instruction. + OPCODE(STORE_FIELD_THIS, 0) + + // Pops an instance and pushes the value of the field in slot [arg] of it. + OPCODE(LOAD_FIELD, 0) + + // Pops an instance and stores the subsequent top of stack in field slot + // [arg] in it. Does not pop the value. + OPCODE(STORE_FIELD, -1) + + // Pop and discard the top of stack. + OPCODE(POP, -1) + + // Invoke the method with symbol [arg]. The number indicates the number of + // arguments (not including the receiver). + OPCODE(CALL_0, 0) OPCODE(CALL_1, -1) OPCODE(CALL_2, -2) OPCODE(CALL_3, -3) OPCODE(CALL_4, -4) OPCODE(CALL_5, -5) + OPCODE(CALL_6, -6) OPCODE(CALL_7, -7) OPCODE(CALL_8, -8) OPCODE(CALL_9, -9) OPCODE(CALL_10, -10) + OPCODE(CALL_11, -11) OPCODE(CALL_12, -12) OPCODE(CALL_13, -13) OPCODE(CALL_14, -14) + OPCODE(CALL_15, -15) OPCODE(CALL_16, -16) + + // Invoke a superclass method with symbol [arg]. The number indicates the + // number of arguments (not including the receiver). + OPCODE(SUPER_0, 0) OPCODE(SUPER_1, -1) OPCODE(SUPER_2, -2) OPCODE(SUPER_3, -3) OPCODE(SUPER_4, -4) + OPCODE(SUPER_5, -5) OPCODE(SUPER_6, -6) OPCODE(SUPER_7, -7) OPCODE(SUPER_8, -8) OPCODE(SUPER_9, -9) + OPCODE(SUPER_10, -10) OPCODE(SUPER_11, -11) OPCODE(SUPER_12, -12) OPCODE(SUPER_13, -13) + OPCODE(SUPER_14, -14) OPCODE(SUPER_15, -15) OPCODE(SUPER_16, -16) + + // Jump the instruction pointer [arg] forward. + OPCODE(JUMP, 0) + + // Jump the instruction pointer [arg] backward. + OPCODE(LOOP, 0) + + // Pop and if not truthy then jump the instruction pointer [arg] forward. + OPCODE(JUMP_IF, -1) + + // If the top of the stack is false, jump [arg] forward. Otherwise, pop and + // continue. + OPCODE(AND, -1) + + // If the top of the stack is non-false, jump [arg] forward. Otherwise, pop + // and continue. + OPCODE(OR, -1) + + // Close the upvalue for the local on the top of the stack, then pop it. + OPCODE(CLOSE_UPVALUE, -1) + + // Exit from the current function and return the value on the top of the + // stack. + OPCODE(RETURN, 0) + + // Creates a closure for the function stored at [arg] in the constant table. + // + // Following the function argument is a number of arguments, two for each + // upvalue. The first is true if the variable being captured is a local (as + // opposed to an upvalue), and the second is the index of the local or + // upvalue being captured. + // + // Pushes the created closure. + OPCODE(CLOSURE, 1) + + // Creates a new instance of a class. + // + // Assumes the class object is in slot zero, and replaces it with the new + // uninitialized instance of that class. This opcode is only emitted by the + // compiler-generated constructor metaclass methods. + OPCODE(CONSTRUCT, 0) + + // Creates a new instance of a foreign class. + // + // Assumes the class object is in slot zero, and replaces it with the new + // uninitialized instance of that class. This opcode is only emitted by the + // compiler-generated constructor metaclass methods. + OPCODE(FOREIGN_CONSTRUCT, 0) + + // Creates a class. Top of stack is the superclass. Below that is a string for + // the name of the class. Byte [arg] is the number of fields in the class. + OPCODE(CLASS, -1) + + // Ends a class. + // Atm the stack contains the class and the ClassAttributes (or null). + OPCODE(END_CLASS, -2) + + // Creates a foreign class. Top of stack is the superclass. Below that is a + // string for the name of the class. + OPCODE(FOREIGN_CLASS, -1) + + // Define a method for symbol [arg]. The class receiving the method is popped + // off the stack, then the function defining the body is popped. + // + // If a foreign method is being defined, the "function" will be a string + // identifying the foreign method. Otherwise, it will be a function or + // closure. + OPCODE(METHOD_INSTANCE, -2) + + // Define a method for symbol [arg]. The class whose metaclass will receive + // the method is popped off the stack, then the function defining the body is + // popped. + // + // If a foreign method is being defined, the "function" will be a string + // identifying the foreign method. Otherwise, it will be a function or + // closure. + OPCODE(METHOD_STATIC, -2) + + // This is executed at the end of the module's body. Pushes NULL onto the stack + // as the "return value" of the import statement and stores the module as the + // most recently imported one. + OPCODE(END_MODULE, 1) + + // Import a module whose name is the string stored at [arg] in the constant + // table. + // + // Pushes null onto the stack so that the fiber for the imported module can + // replace that with a dummy value when it returns. (Fibers always return a + // value when resuming a caller.) + OPCODE(IMPORT_MODULE, 1) + + // Import a variable from the most recently imported module. The name of the + // variable to import is at [arg] in the constant table. Pushes the loaded + // variable's value. + OPCODE(IMPORT_VARIABLE, 1) + + // This pseudo-instruction indicates the end of the bytecode. It should + // always be preceded by a `CODE_RETURN`, so is never actually executed. + OPCODE(END, 0) +// End file "wren_opcodes.h" +#undef OPCODE +}; + +static void printError(Parser* parser, int line, const char* label, const char* format, va_list args) { + parser->hasError = true; + if (!parser->printErrors) return; + + // Only report errors if there is a WrenErrorFn to handle them. + if (parser->vm->config.errorFn == NULL) return; + + // Format the label and message. + char message[ERROR_MESSAGE_SIZE]; + int length = sprintf(message, "%s: ", label); + length += vsprintf(message + length, format, args); + ASSERT(length < ERROR_MESSAGE_SIZE, "Error should not exceed buffer."); + + ObjString* module = parser->module->name; + const char* module_name = module ? module->value : ""; + + parser->vm->config.errorFn(parser->vm, WREN_ERROR_COMPILE, module_name, line, message); +} + +// Outputs a lexical error. +static void lexError(Parser* parser, const char* format, ...) { + va_list args; + va_start(args, format); + printError(parser, parser->currentLine, "Error", format, args); + va_end(args); +} + +// Outputs a compile or syntax error. This also marks the compilation as having +// an error, which ensures that the resulting code will be discarded and never +// run. This means that after calling error(), it's fine to generate whatever +// invalid bytecode you want since it won't be used. +// +// You'll note that most places that call error() continue to parse and compile +// after that. That's so that we can try to find as many compilation errors in +// one pass as possible instead of just bailing at the first one. +static void error(Compiler* compiler, const char* format, ...) { + Token* token = &compiler->parser->previous; + + // If the parse error was caused by an error token, the lexer has already + // reported it. + if (token->type == TOKEN_ERROR) return; + + va_list args; + va_start(args, format); + if (token->type == TOKEN_LINE) { + printError(compiler->parser, token->line, "Error at newline", format, args); + } else if (token->type == TOKEN_EOF) { + printError(compiler->parser, token->line, "Error at end of file", format, args); + } else { + // Make sure we don't exceed the buffer with a very long token. + char label[10 + MAX_VARIABLE_NAME + 4 + 1]; + if (token->length <= MAX_VARIABLE_NAME) { + sprintf(label, "Error at '%.*s'", token->length, token->start); + } else { + sprintf(label, "Error at '%.*s...'", MAX_VARIABLE_NAME, token->start); + } + printError(compiler->parser, token->line, label, format, args); + } + va_end(args); +} + +// Adds [constant] to the constant pool and returns its index. +static int addConstant(Compiler* compiler, Value constant) { + if (compiler->parser->hasError) return -1; + + // See if we already have a constant for the value. If so, reuse it. + if (compiler->constants != NULL) { + Value existing = wrenMapGet(compiler->constants, constant); + if (IS_NUM(existing)) return (int)AS_NUM(existing); + } + + // It's a new constant. + if (compiler->fn->constants.count < MAX_CONSTANTS) { + if (IS_OBJ(constant)) wrenPushRoot(compiler->parser->vm, AS_OBJ(constant)); + wrenValueBufferWrite(compiler->parser->vm, &compiler->fn->constants, constant); + if (IS_OBJ(constant)) wrenPopRoot(compiler->parser->vm); + + if (compiler->constants == NULL) { + compiler->constants = wrenNewMap(compiler->parser->vm); + } + wrenMapSet(compiler->parser->vm, compiler->constants, constant, NUM_VAL(compiler->fn->constants.count - 1)); + } else { + error(compiler, "A function may only contain %d unique constants.", MAX_CONSTANTS); + } + + return compiler->fn->constants.count - 1; +} + +// Initializes [compiler]. +static void initCompiler(Compiler* compiler, Parser* parser, Compiler* parent, bool isMethod) { + compiler->parser = parser; + compiler->parent = parent; + compiler->loop = NULL; + compiler->enclosingClass = NULL; + compiler->isInitializer = false; + + // Initialize these to NULL before allocating in case a GC gets triggered in + // the middle of initializing the compiler. + compiler->fn = NULL; + compiler->constants = NULL; + compiler->attributes = NULL; + + parser->vm->compiler = compiler; + + // Declare a local slot for either the closure or method receiver so that we + // don't try to reuse that slot for a user-defined local variable. For + // methods, we name it "this", so that we can resolve references to that like + // a normal variable. For functions, they have no explicit "this", so we use + // an empty name. That way references to "this" inside a function walks up + // the parent chain to find a method enclosing the function whose "this" we + // can close over. + compiler->numLocals = 1; + compiler->numSlots = compiler->numLocals; + + if (isMethod) { + compiler->locals[0].name = "this"; + compiler->locals[0].length = 4; + } else { + compiler->locals[0].name = NULL; + compiler->locals[0].length = 0; + } + + compiler->locals[0].depth = -1; + compiler->locals[0].isUpvalue = false; + + if (parent == NULL) { + // Compiling top-level code, so the initial scope is module-level. + compiler->scopeDepth = -1; + } else { + // The initial scope for functions and methods is local scope. + compiler->scopeDepth = 0; + } + + compiler->numAttributes = 0; + compiler->attributes = wrenNewMap(parser->vm); + compiler->fn = wrenNewFunction(parser->vm, parser->module, compiler->numLocals); +} + +// Lexing ---------------------------------------------------------------------- + +typedef struct { + const char* identifier; + size_t length; + TokenType tokenType; +} Keyword; + +// The table of reserved words and their associated token types. +static Keyword keywords[] = { + {"break", 5, TOKEN_BREAK}, {"continue", 8, TOKEN_CONTINUE}, + {"class", 5, TOKEN_CLASS}, {"construct", 9, TOKEN_CONSTRUCT}, + {"else", 4, TOKEN_ELSE}, {"false", 5, TOKEN_FALSE}, + {"for", 3, TOKEN_FOR}, {"foreign", 7, TOKEN_FOREIGN}, + {"if", 2, TOKEN_IF}, {"import", 6, TOKEN_IMPORT}, + {"as", 2, TOKEN_AS}, {"in", 2, TOKEN_IN}, + {"is", 2, TOKEN_IS}, {"null", 4, TOKEN_NULL}, + {"return", 6, TOKEN_RETURN}, {"static", 6, TOKEN_STATIC}, + {"super", 5, TOKEN_SUPER}, {"this", 4, TOKEN_THIS}, + {"true", 4, TOKEN_TRUE}, {"var", 3, TOKEN_VAR}, + {"while", 5, TOKEN_WHILE}, {NULL, 0, TOKEN_EOF} // Sentinel to mark the end of the array. +}; + +// Returns true if [c] is a valid (non-initial) identifier character. +static bool isName(char c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'; +} + +// Returns true if [c] is a digit. +static bool isDigit(char c) { + return c >= '0' && c <= '9'; +} + +// Returns the current character the parser is sitting on. +static char peekChar(Parser* parser) { + return *parser->currentChar; +} + +// Returns the character after the current character. +static char peekNextChar(Parser* parser) { + // If we're at the end of the source, don't read past it. + if (peekChar(parser) == '\0') return '\0'; + return *(parser->currentChar + 1); +} + +// Advances the parser forward one character. +static char nextChar(Parser* parser) { + char c = peekChar(parser); + parser->currentChar++; + if (c == '\n') parser->currentLine++; + return c; +} + +// If the current character is [c], consumes it and returns `true`. +static bool matchChar(Parser* parser, char c) { + if (peekChar(parser) != c) return false; + nextChar(parser); + return true; +} + +// Sets the parser's current token to the given [type] and current character +// range. +static void makeToken(Parser* parser, TokenType type) { + parser->next.type = type; + parser->next.start = parser->tokenStart; + parser->next.length = (int)(parser->currentChar - parser->tokenStart); + parser->next.line = parser->currentLine; + + // Make line tokens appear on the line containing the "\n". + if (type == TOKEN_LINE) parser->next.line--; +} + +// If the current character is [c], then consumes it and makes a token of type +// [two]. Otherwise makes a token of type [one]. +static void twoCharToken(Parser* parser, char c, TokenType two, TokenType one) { + makeToken(parser, matchChar(parser, c) ? two : one); +} + +// Skips the rest of the current line. +static void skipLineComment(Parser* parser) { + while (peekChar(parser) != '\n' && peekChar(parser) != '\0') { + nextChar(parser); + } +} + +// Skips the rest of a block comment. +static void skipBlockComment(Parser* parser) { + int nesting = 1; + while (nesting > 0) { + if (peekChar(parser) == '\0') { + lexError(parser, "Unterminated block comment."); + return; + } + + if (peekChar(parser) == '/' && peekNextChar(parser) == '*') { + nextChar(parser); + nextChar(parser); + nesting++; + continue; + } + + if (peekChar(parser) == '*' && peekNextChar(parser) == '/') { + nextChar(parser); + nextChar(parser); + nesting--; + continue; + } + + // Regular comment character. + nextChar(parser); + } +} + +// Reads the next character, which should be a hex digit (0-9, a-f, or A-F) and +// returns its numeric value. If the character isn't a hex digit, returns -1. +static int readHexDigit(Parser* parser) { + char c = nextChar(parser); + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + + // Don't consume it if it isn't expected. Keeps us from reading past the end + // of an unterminated string. + parser->currentChar--; + return -1; +} + +// Parses the numeric value of the current token. +static void makeNumber(Parser* parser, bool isHex) { + errno = 0; + + if (isHex) { + parser->next.value = NUM_VAL((double)strtoll(parser->tokenStart, NULL, 16)); + } else { + parser->next.value = NUM_VAL(strtod(parser->tokenStart, NULL)); + } + + if (errno == ERANGE) { + lexError(parser, "Number literal was too large (%d).", sizeof(long int)); + parser->next.value = NUM_VAL(0); + } + + // We don't check that the entire token is consumed after calling strtoll() + // or strtod() because we've already scanned it ourselves and know it's valid. + + makeToken(parser, TOKEN_NUMBER); +} + +// Finishes lexing a hexadecimal number literal. +static void readHexNumber(Parser* parser) { + // Skip past the `x` used to denote a hexadecimal literal. + nextChar(parser); + + // Iterate over all the valid hexadecimal digits found. + while (readHexDigit(parser) != -1) continue; + + makeNumber(parser, true); +} + +// Finishes lexing a number literal. +static void readNumber(Parser* parser) { + while (isDigit(peekChar(parser))) nextChar(parser); + + // See if it has a floating point. Make sure there is a digit after the "." + // so we don't get confused by method calls on number literals. + if (peekChar(parser) == '.' && isDigit(peekNextChar(parser))) { + nextChar(parser); + while (isDigit(peekChar(parser))) nextChar(parser); + } + + // See if the number is in scientific notation. + if (matchChar(parser, 'e') || matchChar(parser, 'E')) { + // Allow a single positive/negative exponent symbol. + if (!matchChar(parser, '+')) { + matchChar(parser, '-'); + } + + if (!isDigit(peekChar(parser))) { + lexError(parser, "Unterminated scientific notation."); + } + + while (isDigit(peekChar(parser))) nextChar(parser); + } + + makeNumber(parser, false); +} + +// Finishes lexing an identifier. Handles reserved words. +static void readName(Parser* parser, TokenType type, char firstChar) { + ByteBuffer string; + wrenByteBufferInit(&string); + wrenByteBufferWrite(parser->vm, &string, firstChar); + + while (isName(peekChar(parser)) || isDigit(peekChar(parser))) { + char c = nextChar(parser); + wrenByteBufferWrite(parser->vm, &string, c); + } + + // Update the type if it's a keyword. + size_t length = parser->currentChar - parser->tokenStart; + for (int i = 0; keywords[i].identifier != NULL; i++) { + if (length == keywords[i].length && memcmp(parser->tokenStart, keywords[i].identifier, length) == 0) { + type = keywords[i].tokenType; + break; + } + } + + parser->next.value = wrenNewStringLength(parser->vm, (char*)string.data, string.count); + + wrenByteBufferClear(parser->vm, &string); + makeToken(parser, type); +} + +// Reads [digits] hex digits in a string literal and returns their number value. +static int readHexEscape(Parser* parser, int digits, const char* description) { + int value = 0; + for (int i = 0; i < digits; i++) { + if (peekChar(parser) == '"' || peekChar(parser) == '\0') { + lexError(parser, "Incomplete %s escape sequence.", description); + + // Don't consume it if it isn't expected. Keeps us from reading past the + // end of an unterminated string. + parser->currentChar--; + break; + } + + int digit = readHexDigit(parser); + if (digit == -1) { + lexError(parser, "Invalid %s escape sequence.", description); + break; + } + + value = (value * 16) | digit; + } + + return value; +} + +// Reads a hex digit Unicode escape sequence in a string literal. +static void readUnicodeEscape(Parser* parser, ByteBuffer* string, int length) { + int value = readHexEscape(parser, length, "Unicode"); + + // Grow the buffer enough for the encoded result. + int numBytes = wrenUtf8EncodeNumBytes(value); + if (numBytes != 0) { + wrenByteBufferFill(parser->vm, string, 0, numBytes); + wrenUtf8Encode(value, string->data + string->count - numBytes); + } +} + +static void readRawString(Parser* parser) { + ByteBuffer string; + wrenByteBufferInit(&string); + TokenType type = TOKEN_STRING; + + //consume the second and third " + nextChar(parser); + nextChar(parser); + + int skipStart = 0; + int firstNewline = -1; + + int skipEnd = -1; + int lastNewline = -1; + + for (;;) { + char c = nextChar(parser); + char c1 = peekChar(parser); + char c2 = peekNextChar(parser); + + if (c == '\r') continue; + + if (c == '\n') { + lastNewline = string.count; + skipEnd = lastNewline; + firstNewline = firstNewline == -1 ? string.count : firstNewline; + } + + if (c == '"' && c1 == '"' && c2 == '"') break; + + bool isWhitespace = c == ' ' || c == '\t'; + skipEnd = c == '\n' || isWhitespace ? skipEnd : -1; + + // If we haven't seen a newline or other character yet, + // and still seeing whitespace, count the characters + // as skippable till we know otherwise + bool skippable = skipStart != -1 && isWhitespace && firstNewline == -1; + skipStart = skippable ? string.count + 1 : skipStart; + + // We've counted leading whitespace till we hit something else, + // but it's not a newline, so we reset skipStart since we need these characters + if (firstNewline == -1 && !isWhitespace && c != '\n') skipStart = -1; + + if (c == '\0' || c1 == '\0' || c2 == '\0') { + lexError(parser, "Unterminated raw string."); + + // Don't consume it if it isn't expected. Keeps us from reading past the + // end of an unterminated string. + parser->currentChar--; + break; + } + + wrenByteBufferWrite(parser->vm, &string, c); + } + + //consume the second and third " + nextChar(parser); + nextChar(parser); + + int offset = 0; + int count = string.count; + + if (firstNewline != -1 && skipStart == firstNewline) offset = firstNewline + 1; + if (lastNewline != -1 && skipEnd == lastNewline) count = lastNewline; + + count -= (offset > count) ? count : offset; + + parser->next.value = wrenNewStringLength(parser->vm, ((char*)string.data) + offset, count); + + wrenByteBufferClear(parser->vm, &string); + makeToken(parser, type); +} + +// Finishes lexing a string literal. +static void readString(Parser* parser) { + ByteBuffer string; + TokenType type = TOKEN_STRING; + wrenByteBufferInit(&string); + + for (;;) { + char c = nextChar(parser); + if (c == '"') break; + if (c == '\r') continue; + + if (c == '\0') { + lexError(parser, "Unterminated string."); + + // Don't consume it if it isn't expected. Keeps us from reading past the + // end of an unterminated string. + parser->currentChar--; + break; + } + + if (c == '%') { + if (parser->numParens < MAX_INTERPOLATION_NESTING) { + // TODO: Allow format string. + if (nextChar(parser) != '(') lexError(parser, "Expect '(' after '%%'."); + + parser->parens[parser->numParens++] = 1; + type = TOKEN_INTERPOLATION; + break; + } + + lexError(parser, "Interpolation may only nest %d levels deep.", MAX_INTERPOLATION_NESTING); + } + + if (c == '\\') { + switch (nextChar(parser)) { + case '"': + wrenByteBufferWrite(parser->vm, &string, '"'); + break; + case '\\': + wrenByteBufferWrite(parser->vm, &string, '\\'); + break; + case '%': + wrenByteBufferWrite(parser->vm, &string, '%'); + break; + case '0': + wrenByteBufferWrite(parser->vm, &string, '\0'); + break; + case 'a': + wrenByteBufferWrite(parser->vm, &string, '\a'); + break; + case 'b': + wrenByteBufferWrite(parser->vm, &string, '\b'); + break; + case 'e': + wrenByteBufferWrite(parser->vm, &string, '\33'); + break; + case 'f': + wrenByteBufferWrite(parser->vm, &string, '\f'); + break; + case 'n': + wrenByteBufferWrite(parser->vm, &string, '\n'); + break; + case 'r': + wrenByteBufferWrite(parser->vm, &string, '\r'); + break; + case 't': + wrenByteBufferWrite(parser->vm, &string, '\t'); + break; + case 'u': + readUnicodeEscape(parser, &string, 4); + break; + case 'U': + readUnicodeEscape(parser, &string, 8); + break; + case 'v': + wrenByteBufferWrite(parser->vm, &string, '\v'); + break; + case 'x': + wrenByteBufferWrite(parser->vm, &string, (uint8_t)readHexEscape(parser, 2, "byte")); + break; + + default: + lexError(parser, "Invalid escape character '%c'.", *(parser->currentChar - 1)); + break; + } + } else { + wrenByteBufferWrite(parser->vm, &string, c); + } + } + + parser->next.value = wrenNewStringLength(parser->vm, (char*)string.data, string.count); + + wrenByteBufferClear(parser->vm, &string); + makeToken(parser, type); +} + +// Lex the next token and store it in [parser.next]. +static void nextToken(Parser* parser) { + parser->previous = parser->current; + parser->current = parser->next; + + // If we are out of tokens, don't try to tokenize any more. We *do* still + // copy the TOKEN_EOF to previous so that code that expects it to be consumed + // will still work. + if (parser->next.type == TOKEN_EOF) return; + if (parser->current.type == TOKEN_EOF) return; + + while (peekChar(parser) != '\0') { + parser->tokenStart = parser->currentChar; + + char c = nextChar(parser); + switch (c) { + case '(': + // If we are inside an interpolated expression, count the unmatched "(". + if (parser->numParens > 0) parser->parens[parser->numParens - 1]++; + makeToken(parser, TOKEN_LEFT_PAREN); + return; + + case ')': + // If we are inside an interpolated expression, count the ")". + if (parser->numParens > 0 && --parser->parens[parser->numParens - 1] == 0) { + // This is the final ")", so the interpolation expression has ended. + // This ")" now begins the next section of the template string. + parser->numParens--; + readString(parser); + return; + } + + makeToken(parser, TOKEN_RIGHT_PAREN); + return; + + case '[': + makeToken(parser, TOKEN_LEFT_BRACKET); + return; + case ']': + makeToken(parser, TOKEN_RIGHT_BRACKET); + return; + case '{': + makeToken(parser, TOKEN_LEFT_BRACE); + return; + case '}': + makeToken(parser, TOKEN_RIGHT_BRACE); + return; + case ':': + makeToken(parser, TOKEN_COLON); + return; + case ',': + makeToken(parser, TOKEN_COMMA); + return; + case '*': + makeToken(parser, TOKEN_STAR); + return; + case '%': + makeToken(parser, TOKEN_PERCENT); + return; + case '#': { + // Ignore shebang on the first line. + if (parser->currentLine == 1 && peekChar(parser) == '!' && peekNextChar(parser) == '/') { + skipLineComment(parser); + break; + } + // Otherwise we treat it as a token + makeToken(parser, TOKEN_HASH); + return; + } + case '^': + makeToken(parser, TOKEN_CARET); + return; + case '+': + makeToken(parser, TOKEN_PLUS); + return; + case '-': + makeToken(parser, TOKEN_MINUS); + return; + case '~': + makeToken(parser, TOKEN_TILDE); + return; + case '?': + makeToken(parser, TOKEN_QUESTION); + return; + + case '|': + twoCharToken(parser, '|', TOKEN_PIPEPIPE, TOKEN_PIPE); + return; + case '&': + twoCharToken(parser, '&', TOKEN_AMPAMP, TOKEN_AMP); + return; + case '=': + twoCharToken(parser, '=', TOKEN_EQEQ, TOKEN_EQ); + return; + case '!': + twoCharToken(parser, '=', TOKEN_BANGEQ, TOKEN_BANG); + return; + + case '.': + if (matchChar(parser, '.')) { + twoCharToken(parser, '.', TOKEN_DOTDOTDOT, TOKEN_DOTDOT); + return; + } + + makeToken(parser, TOKEN_DOT); + return; + + case '/': + if (matchChar(parser, '/')) { + skipLineComment(parser); + break; + } + + if (matchChar(parser, '*')) { + skipBlockComment(parser); + break; + } + + makeToken(parser, TOKEN_SLASH); + return; + + case '<': + if (matchChar(parser, '<')) { + makeToken(parser, TOKEN_LTLT); + } else { + twoCharToken(parser, '=', TOKEN_LTEQ, TOKEN_LT); + } + return; + + case '>': + if (matchChar(parser, '>')) { + makeToken(parser, TOKEN_GTGT); + } else { + twoCharToken(parser, '=', TOKEN_GTEQ, TOKEN_GT); + } + return; + + case '\n': + makeToken(parser, TOKEN_LINE); + return; + + case ' ': + case '\r': + case '\t': + // Skip forward until we run out of whitespace. + while (peekChar(parser) == ' ' || peekChar(parser) == '\r' || peekChar(parser) == '\t') { + nextChar(parser); + } + break; + + case '"': { + if (peekChar(parser) == '"' && peekNextChar(parser) == '"') { + readRawString(parser); + return; + } + readString(parser); + return; + } + case '_': + readName(parser, peekChar(parser) == '_' ? TOKEN_STATIC_FIELD : TOKEN_FIELD, c); + return; + + case '0': + if (peekChar(parser) == 'x') { + readHexNumber(parser); + return; + } + + readNumber(parser); + return; + + default: + if (isName(c)) { + readName(parser, TOKEN_NAME, c); + } else if (isDigit(c)) { + readNumber(parser); + } else { + if (c >= 32 && c <= 126) { + lexError(parser, "Invalid character '%c'.", c); + } else { + // Don't show non-ASCII values since we didn't UTF-8 decode the + // bytes. Since there are no non-ASCII byte values that are + // meaningful code units in Wren, the lexer works on raw bytes, + // even though the source code and console output are UTF-8. + lexError(parser, "Invalid byte 0x%x.", (uint8_t)c); + } + parser->next.type = TOKEN_ERROR; + parser->next.length = 0; + } + return; + } + } + + // If we get here, we're out of source, so just make EOF tokens. + parser->tokenStart = parser->currentChar; + makeToken(parser, TOKEN_EOF); +} + +// Parsing --------------------------------------------------------------------- + +// Returns the type of the current token. +static TokenType peek(Compiler* compiler) { + return compiler->parser->current.type; +} + +// Returns the type of the current token. +static TokenType peekNext(Compiler* compiler) { + return compiler->parser->next.type; +} + +// Consumes the current token if its type is [expected]. Returns true if a +// token was consumed. +static bool match(Compiler* compiler, TokenType expected) { + if (peek(compiler) != expected) return false; + + nextToken(compiler->parser); + return true; +} + +// Consumes the current token. Emits an error if its type is not [expected]. +static void consume(Compiler* compiler, TokenType expected, const char* errorMessage) { + nextToken(compiler->parser); + if (compiler->parser->previous.type != expected) { + error(compiler, errorMessage); + + // If the next token is the one we want, assume the current one is just a + // spurious error and discard it to minimize the number of cascaded errors. + if (compiler->parser->current.type == expected) nextToken(compiler->parser); + } +} + +// Matches one or more newlines. Returns true if at least one was found. +static bool matchLine(Compiler* compiler) { + if (!match(compiler, TOKEN_LINE)) return false; + + while (match(compiler, TOKEN_LINE)) + ; + return true; +} + +// Discards any newlines starting at the current token. +static void ignoreNewlines(Compiler* compiler) { + matchLine(compiler); +} + +// Consumes the current token. Emits an error if it is not a newline. Then +// discards any duplicate newlines following it. +static void consumeLine(Compiler* compiler, const char* errorMessage) { + consume(compiler, TOKEN_LINE, errorMessage); + ignoreNewlines(compiler); +} + +static void allowLineBeforeDot(Compiler* compiler) { + if (peek(compiler) == TOKEN_LINE && peekNext(compiler) == TOKEN_DOT) { + nextToken(compiler->parser); + } +} + +// Variables and scopes -------------------------------------------------------- + +// Emits one single-byte argument. Returns its index. +static int emitByte(Compiler* compiler, int byte) { + wrenByteBufferWrite(compiler->parser->vm, &compiler->fn->code, (uint8_t)byte); + + // Assume the instruction is associated with the most recently consumed token. + wrenIntBufferWrite(compiler->parser->vm, &compiler->fn->debug->sourceLines, compiler->parser->previous.line); + + return compiler->fn->code.count - 1; +} + +// Emits one bytecode instruction. +static void emitOp(Compiler* compiler, Code instruction) { + emitByte(compiler, instruction); + + // Keep track of the stack's high water mark. + compiler->numSlots += stackEffects[instruction]; + if (compiler->numSlots > compiler->fn->maxSlots) { + compiler->fn->maxSlots = compiler->numSlots; + } +} + +// Emits one 16-bit argument, which will be written big endian. +static void emitShort(Compiler* compiler, int arg) { + emitByte(compiler, (arg >> 8) & 0xff); + emitByte(compiler, arg & 0xff); +} + +// Emits one bytecode instruction followed by a 8-bit argument. Returns the +// index of the argument in the bytecode. +static int emitByteArg(Compiler* compiler, Code instruction, int arg) { + emitOp(compiler, instruction); + return emitByte(compiler, arg); +} + +// Emits one bytecode instruction followed by a 16-bit argument, which will be +// written big endian. +static void emitShortArg(Compiler* compiler, Code instruction, int arg) { + emitOp(compiler, instruction); + emitShort(compiler, arg); +} + +// Emits [instruction] followed by a placeholder for a jump offset. The +// placeholder can be patched by calling [jumpPatch]. Returns the index of the +// placeholder. +static int emitJump(Compiler* compiler, Code instruction) { + emitOp(compiler, instruction); + emitByte(compiler, 0xff); + return emitByte(compiler, 0xff) - 1; +} + +// Creates a new constant for the current value and emits the bytecode to load +// it from the constant table. +static void emitConstant(Compiler* compiler, Value value) { + int constant = addConstant(compiler, value); + + // Compile the code to load the constant. + emitShortArg(compiler, CODE_CONSTANT, constant); +} + +// Create a new local variable with [name]. Assumes the current scope is local +// and the name is unique. +static int addLocal(Compiler* compiler, const char* name, int length) { + Local* local = &compiler->locals[compiler->numLocals]; + local->name = name; + local->length = length; + local->depth = compiler->scopeDepth; + local->isUpvalue = false; + return compiler->numLocals++; +} + +// Declares a variable in the current scope whose name is the given token. +// +// If [token] is `NULL`, uses the previously consumed token. Returns its symbol. +static int declareVariable(Compiler* compiler, Token* token) { + if (token == NULL) token = &compiler->parser->previous; + + if (token->length > MAX_VARIABLE_NAME) { + error(compiler, "Variable name cannot be longer than %d characters.", MAX_VARIABLE_NAME); + } + + // Top-level module scope. + if (compiler->scopeDepth == -1) { + int line = -1; + int symbol = wrenDefineVariable(compiler->parser->vm, compiler->parser->module, token->start, token->length, + NULL_VAL, &line); + + if (symbol == -1) { + error(compiler, "Module variable is already defined."); + } else if (symbol == -2) { + error(compiler, "Too many module variables defined."); + } else if (symbol == -3) { + error(compiler, "Variable '%.*s' referenced before this definition (first use at line %d).", token->length, + token->start, line); + } + + return symbol; + } + + // See if there is already a variable with this name declared in the current + // scope. (Outer scopes are OK: those get shadowed.) + for (int i = compiler->numLocals - 1; i >= 0; i--) { + Local* local = &compiler->locals[i]; + + // Once we escape this scope and hit an outer one, we can stop. + if (local->depth < compiler->scopeDepth) break; + + if (local->length == token->length && memcmp(local->name, token->start, token->length) == 0) { + error(compiler, "Variable is already declared in this scope."); + return i; + } + } + + if (compiler->numLocals == MAX_LOCALS) { + error(compiler, "Cannot declare more than %d variables in one scope.", MAX_LOCALS); + return -1; + } + + return addLocal(compiler, token->start, token->length); +} + +// Parses a name token and declares a variable in the current scope with that +// name. Returns its slot. +static int declareNamedVariable(Compiler* compiler) { + consume(compiler, TOKEN_NAME, "Expect variable name."); + return declareVariable(compiler, NULL); +} + +// Stores a variable with the previously defined symbol in the current scope. +static void defineVariable(Compiler* compiler, int symbol) { + // Store the variable. If it's a local, the result of the initializer is + // in the correct slot on the stack already so we're done. + if (compiler->scopeDepth >= 0) return; + + // It's a module-level variable, so store the value in the module slot and + // then discard the temporary for the initializer. + emitShortArg(compiler, CODE_STORE_MODULE_VAR, symbol); + emitOp(compiler, CODE_POP); +} + +// Starts a new local block scope. +static void pushScope(Compiler* compiler) { + compiler->scopeDepth++; +} + +// Generates code to discard local variables at [depth] or greater. Does *not* +// actually undeclare variables or pop any scopes, though. This is called +// directly when compiling "break" statements to ditch the local variables +// before jumping out of the loop even though they are still in scope *past* +// the break instruction. +// +// Returns the number of local variables that were eliminated. +static int discardLocals(Compiler* compiler, int depth) { + ASSERT(compiler->scopeDepth > -1, "Cannot exit top-level scope."); + + int local = compiler->numLocals - 1; + while (local >= 0 && compiler->locals[local].depth >= depth) { + // If the local was closed over, make sure the upvalue gets closed when it + // goes out of scope on the stack. We use emitByte() and not emitOp() here + // because we don't want to track that stack effect of these pops since the + // variables are still in scope after the break. + if (compiler->locals[local].isUpvalue) { + emitByte(compiler, CODE_CLOSE_UPVALUE); + } else { + emitByte(compiler, CODE_POP); + } + + local--; + } + + return compiler->numLocals - local - 1; +} + +// Closes the last pushed block scope and discards any local variables declared +// in that scope. This should only be called in a statement context where no +// temporaries are still on the stack. +static void popScope(Compiler* compiler) { + int popped = discardLocals(compiler, compiler->scopeDepth); + compiler->numLocals -= popped; + compiler->numSlots -= popped; + compiler->scopeDepth--; +} + +// Attempts to look up the name in the local variables of [compiler]. If found, +// returns its index, otherwise returns -1. +static int resolveLocal(Compiler* compiler, const char* name, int length) { + // Look it up in the local scopes. Look in reverse order so that the most + // nested variable is found first and shadows outer ones. + for (int i = compiler->numLocals - 1; i >= 0; i--) { + if (compiler->locals[i].length == length && memcmp(name, compiler->locals[i].name, length) == 0) { + return i; + } + } + + return -1; +} + +// Adds an upvalue to [compiler]'s function with the given properties. Does not +// add one if an upvalue for that variable is already in the list. Returns the +// index of the upvalue. +static int addUpvalue(Compiler* compiler, bool isLocal, int index) { + // Look for an existing one. + for (int i = 0; i < compiler->fn->numUpvalues; i++) { + CompilerUpvalue* upvalue = &compiler->upvalues[i]; + if (upvalue->index == index && upvalue->isLocal == isLocal) return i; + } + + // If we got here, it's a new upvalue. + compiler->upvalues[compiler->fn->numUpvalues].isLocal = isLocal; + compiler->upvalues[compiler->fn->numUpvalues].index = index; + return compiler->fn->numUpvalues++; +} + +// Attempts to look up [name] in the functions enclosing the one being compiled +// by [compiler]. If found, it adds an upvalue for it to this compiler's list +// of upvalues (unless it's already in there) and returns its index. If not +// found, returns -1. +// +// If the name is found outside of the immediately enclosing function, this +// will flatten the closure and add upvalues to all of the intermediate +// functions so that it gets walked down to this one. +// +// If it reaches a method boundary, this stops and returns -1 since methods do +// not close over local variables. +static int findUpvalue(Compiler* compiler, const char* name, int length) { + // If we are at the top level, we didn't find it. + if (compiler->parent == NULL) return -1; + + // If we hit the method boundary (and the name isn't a static field), then + // stop looking for it. We'll instead treat it as a self send. + if (name[0] != '_' && compiler->parent->enclosingClass != NULL) return -1; + + // See if it's a local variable in the immediately enclosing function. + int local = resolveLocal(compiler->parent, name, length); + if (local != -1) { + // Mark the local as an upvalue so we know to close it when it goes out of + // scope. + compiler->parent->locals[local].isUpvalue = true; + + return addUpvalue(compiler, true, local); + } + + // See if it's an upvalue in the immediately enclosing function. In other + // words, if it's a local variable in a non-immediately enclosing function. + // This "flattens" closures automatically: it adds upvalues to all of the + // intermediate functions to get from the function where a local is declared + // all the way into the possibly deeply nested function that is closing over + // it. + int upvalue = findUpvalue(compiler->parent, name, length); + if (upvalue != -1) { + return addUpvalue(compiler, false, upvalue); + } + + // If we got here, we walked all the way up the parent chain and couldn't + // find it. + return -1; +} + +// Look up [name] in the current scope to see what variable it refers to. +// Returns the variable either in local scope, or the enclosing function's +// upvalue list. Does not search the module scope. Returns a variable with +// index -1 if not found. +static Variable resolveNonmodule(Compiler* compiler, const char* name, int length) { + // Look it up in the local scopes. + Variable variable; + variable.scope = SCOPE_LOCAL; + variable.index = resolveLocal(compiler, name, length); + if (variable.index != -1) return variable; + + // Tt's not a local, so guess that it's an upvalue. + variable.scope = SCOPE_UPVALUE; + variable.index = findUpvalue(compiler, name, length); + return variable; +} + +// Look up [name] in the current scope to see what variable it refers to. +// Returns the variable either in module scope, local scope, or the enclosing +// function's upvalue list. Returns a variable with index -1 if not found. +static Variable resolveName(Compiler* compiler, const char* name, int length) { + Variable variable = resolveNonmodule(compiler, name, length); + if (variable.index != -1) return variable; + + variable.scope = SCOPE_MODULE; + variable.index = wrenSymbolTableFind(&compiler->parser->module->variableNames, name, length); + return variable; +} + +static void loadLocal(Compiler* compiler, int slot) { + if (slot <= 8) { + emitOp(compiler, (Code)(CODE_LOAD_LOCAL_0 + slot)); + return; + } + + emitByteArg(compiler, CODE_LOAD_LOCAL, slot); +} + +// Finishes [compiler], which is compiling a function, method, or chunk of top +// level code. If there is a parent compiler, then this emits code in the +// parent compiler to load the resulting function. +static ObjFn* endCompiler(Compiler* compiler, const char* debugName, int debugNameLength) { + // If we hit an error, don't finish the function since it's borked anyway. + if (compiler->parser->hasError) { + compiler->parser->vm->compiler = compiler->parent; + return NULL; + } + + // Mark the end of the bytecode. Since it may contain multiple early returns, + // we can't rely on CODE_RETURN to tell us we're at the end. + emitOp(compiler, CODE_END); + + wrenFunctionBindName(compiler->parser->vm, compiler->fn, debugName, debugNameLength); + + // In the function that contains this one, load the resulting function object. + if (compiler->parent != NULL) { + int constant = addConstant(compiler->parent, OBJ_VAL(compiler->fn)); + + // Wrap the function in a closure. We do this even if it has no upvalues so + // that the VM can uniformly assume all called objects are closures. This + // makes creating a function a little slower, but makes invoking them + // faster. Given that functions are invoked more often than they are + // created, this is a win. + emitShortArg(compiler->parent, CODE_CLOSURE, constant); + + // Emit arguments for each upvalue to know whether to capture a local or + // an upvalue. + for (int i = 0; i < compiler->fn->numUpvalues; i++) { + emitByte(compiler->parent, compiler->upvalues[i].isLocal ? 1 : 0); + emitByte(compiler->parent, compiler->upvalues[i].index); + } + } + + // Pop this compiler off the stack. + compiler->parser->vm->compiler = compiler->parent; + +#if WREN_DEBUG_DUMP_COMPILED_CODE + wrenDumpCode(compiler->parser->vm, compiler->fn); +#endif + + return compiler->fn; +} + +// Grammar --------------------------------------------------------------------- + +typedef enum { + PREC_NONE, + PREC_LOWEST, + PREC_ASSIGNMENT, // = + PREC_CONDITIONAL, // ?: + PREC_LOGICAL_OR, // || + PREC_LOGICAL_AND, // && + PREC_EQUALITY, // == != + PREC_IS, // is + PREC_COMPARISON, // < > <= >= + PREC_BITWISE_OR, // | + PREC_BITWISE_XOR, // ^ + PREC_BITWISE_AND, // & + PREC_BITWISE_SHIFT, // << >> + PREC_RANGE, // .. ... + PREC_TERM, // + - + PREC_FACTOR, // * / % + PREC_UNARY, // unary - ! ~ + PREC_CALL, // . () [] + PREC_PRIMARY +} Precedence; + +typedef void (*GrammarFn)(Compiler*, bool canAssign); + +typedef void (*SignatureFn)(Compiler* compiler, Signature* signature); + +typedef struct { + GrammarFn prefix; + GrammarFn infix; + SignatureFn method; + Precedence precedence; + const char* name; +} GrammarRule; + +// Forward declarations since the grammar is recursive. +static GrammarRule* getRule(TokenType type); +static void expression(Compiler* compiler); +static void statement(Compiler* compiler); +static void definition(Compiler* compiler); +static void parsePrecedence(Compiler* compiler, Precedence precedence); + +// Replaces the placeholder argument for a previous CODE_JUMP or CODE_JUMP_IF +// instruction with an offset that jumps to the current end of bytecode. +static void patchJump(Compiler* compiler, int offset) { + // -2 to adjust for the bytecode for the jump offset itself. + int jump = compiler->fn->code.count - offset - 2; + if (jump > MAX_JUMP) error(compiler, "Too much code to jump over."); + + compiler->fn->code.data[offset] = (jump >> 8) & 0xff; + compiler->fn->code.data[offset + 1] = jump & 0xff; +} + +// Parses a block body, after the initial "{" has been consumed. +// +// Returns true if it was a expression body, false if it was a statement body. +// (More precisely, returns true if a value was left on the stack. An empty +// block returns false.) +static bool finishBlock(Compiler* compiler) { + // Empty blocks do nothing. + if (match(compiler, TOKEN_RIGHT_BRACE)) return false; + + // If there's no line after the "{", it's a single-expression body. + if (!matchLine(compiler)) { + expression(compiler); + consume(compiler, TOKEN_RIGHT_BRACE, "Expect '}' at end of block."); + return true; + } + + // Empty blocks (with just a newline inside) do nothing. + if (match(compiler, TOKEN_RIGHT_BRACE)) return false; + + // Compile the definition list. + do { + definition(compiler); + consumeLine(compiler, "Expect newline after statement."); + } while (peek(compiler) != TOKEN_RIGHT_BRACE && peek(compiler) != TOKEN_EOF); + + consume(compiler, TOKEN_RIGHT_BRACE, "Expect '}' at end of block."); + return false; +} + +// Parses a method or function body, after the initial "{" has been consumed. +// +// If [Compiler->isInitializer] is `true`, this is the body of a constructor +// initializer. In that case, this adds the code to ensure it returns `this`. +static void finishBody(Compiler* compiler) { + bool isExpressionBody = finishBlock(compiler); + + if (compiler->isInitializer) { + // If the initializer body evaluates to a value, discard it. + if (isExpressionBody) emitOp(compiler, CODE_POP); + + // The receiver is always stored in the first local slot. + emitOp(compiler, CODE_LOAD_LOCAL_0); + } else if (!isExpressionBody) { + // Implicitly return null in statement bodies. + emitOp(compiler, CODE_NULL); + } + + emitOp(compiler, CODE_RETURN); +} + +// The VM can only handle a certain number of parameters, so check that we +// haven't exceeded that and give a usable error. +static void validateNumParameters(Compiler* compiler, int numArgs) { + if (numArgs == MAX_PARAMETERS + 1) { + // Only show an error at exactly max + 1 so that we can keep parsing the + // parameters and minimize cascaded errors. + error(compiler, "Methods cannot have more than %d parameters.", MAX_PARAMETERS); + } +} + +// Parses the rest of a comma-separated parameter list after the opening +// delimeter. Updates `arity` in [signature] with the number of parameters. +static void finishParameterList(Compiler* compiler, Signature* signature) { + do { + ignoreNewlines(compiler); + validateNumParameters(compiler, ++signature->arity); + + // Define a local variable in the method for the parameter. + declareNamedVariable(compiler); + } while (match(compiler, TOKEN_COMMA)); +} + +// Gets the symbol for a method [name] with [length]. +static int methodSymbol(Compiler* compiler, const char* name, int length) { + return wrenSymbolTableEnsure(compiler->parser->vm, &compiler->parser->vm->methodNames, name, length); +} + +// Appends characters to [name] (and updates [length]) for [numParams] "_" +// surrounded by [leftBracket] and [rightBracket]. +static void signatureParameterList(char name[MAX_METHOD_SIGNATURE], int* length, int numParams, char leftBracket, + char rightBracket) { + name[(*length)++] = leftBracket; + + // This function may be called with too many parameters. When that happens, + // a compile error has already been reported, but we need to make sure we + // don't overflow the string too, hence the MAX_PARAMETERS check. + for (int i = 0; i < numParams && i < MAX_PARAMETERS; i++) { + if (i > 0) name[(*length)++] = ','; + name[(*length)++] = '_'; + } + name[(*length)++] = rightBracket; +} + +// Fills [name] with the stringified version of [signature] and updates +// [length] to the resulting length. +static void signatureToString(Signature* signature, char name[MAX_METHOD_SIGNATURE], int* length) { + *length = 0; + + // Build the full name from the signature. + memcpy(name + *length, signature->name, signature->length); + *length += signature->length; + + switch (signature->type) { + case SIG_METHOD: + signatureParameterList(name, length, signature->arity, '(', ')'); + break; + + case SIG_GETTER: + // The signature is just the name. + break; + + case SIG_SETTER: + name[(*length)++] = '='; + signatureParameterList(name, length, 1, '(', ')'); + break; + + case SIG_SUBSCRIPT: + signatureParameterList(name, length, signature->arity, '[', ']'); + break; + + case SIG_SUBSCRIPT_SETTER: + signatureParameterList(name, length, signature->arity - 1, '[', ']'); + name[(*length)++] = '='; + signatureParameterList(name, length, 1, '(', ')'); + break; + + case SIG_INITIALIZER: + memcpy(name, "init ", 5); + memcpy(name + 5, signature->name, signature->length); + *length = 5 + signature->length; + signatureParameterList(name, length, signature->arity, '(', ')'); + break; + } + + name[*length] = '\0'; +} + +// Gets the symbol for a method with [signature]. +static int signatureSymbol(Compiler* compiler, Signature* signature) { + // Build the full name from the signature. + char name[MAX_METHOD_SIGNATURE]; + int length; + signatureToString(signature, name, &length); + + return methodSymbol(compiler, name, length); +} + +// Returns a signature with [type] whose name is from the last consumed token. +static Signature signatureFromToken(Compiler* compiler, SignatureType type) { + Signature signature; + + // Get the token for the method name. + Token* token = &compiler->parser->previous; + signature.name = token->start; + signature.length = token->length; + signature.type = type; + signature.arity = 0; + + if (signature.length > MAX_METHOD_NAME) { + error(compiler, "Method names cannot be longer than %d characters.", MAX_METHOD_NAME); + signature.length = MAX_METHOD_NAME; + } + + return signature; +} + +// Parses a comma-separated list of arguments. Modifies [signature] to include +// the arity of the argument list. +static void finishArgumentList(Compiler* compiler, Signature* signature) { + do { + ignoreNewlines(compiler); + validateNumParameters(compiler, ++signature->arity); + expression(compiler); + } while (match(compiler, TOKEN_COMMA)); + + // Allow a newline before the closing delimiter. + ignoreNewlines(compiler); +} + +// Compiles a method call with [signature] using [instruction]. +static void callSignature(Compiler* compiler, Code instruction, Signature* signature) { + int symbol = signatureSymbol(compiler, signature); + emitShortArg(compiler, (Code)(instruction + signature->arity), symbol); + + if (instruction == CODE_SUPER_0) { + // Super calls need to be statically bound to the class's superclass. This + // ensures we call the right method even when a method containing a super + // call is inherited by another subclass. + // + // We bind it at class definition time by storing a reference to the + // superclass in a constant. So, here, we create a slot in the constant + // table and store NULL in it. When the method is bound, we'll look up the + // superclass then and store it in the constant slot. + emitShort(compiler, addConstant(compiler, NULL_VAL)); + } +} + +// Compiles a method call with [numArgs] for a method with [name] with [length]. +static void callMethod(Compiler* compiler, int numArgs, const char* name, int length) { + int symbol = methodSymbol(compiler, name, length); + emitShortArg(compiler, (Code)(CODE_CALL_0 + numArgs), symbol); +} + +// Compiles an (optional) argument list for a method call with [methodSignature] +// and then calls it. +static void methodCall(Compiler* compiler, Code instruction, Signature* signature) { + // Make a new signature that contains the updated arity and type based on + // the arguments we find. + Signature called = {signature->name, signature->length, SIG_GETTER, 0}; + + // Parse the argument list, if any. + if (match(compiler, TOKEN_LEFT_PAREN)) { + called.type = SIG_METHOD; + + // Allow new line before an empty argument list + ignoreNewlines(compiler); + + // Allow empty an argument list. + if (peek(compiler) != TOKEN_RIGHT_PAREN) { + finishArgumentList(compiler, &called); + } + consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after arguments."); + } + + // Parse the block argument, if any. + if (match(compiler, TOKEN_LEFT_BRACE)) { + // Include the block argument in the arity. + called.type = SIG_METHOD; + called.arity++; + + Compiler fnCompiler; + initCompiler(&fnCompiler, compiler->parser, compiler, false); + + // Make a dummy signature to track the arity. + Signature fnSignature = {"", 0, SIG_METHOD, 0}; + + // Parse the parameter list, if any. + if (match(compiler, TOKEN_PIPE)) { + finishParameterList(&fnCompiler, &fnSignature); + consume(compiler, TOKEN_PIPE, "Expect '|' after function parameters."); + } + + fnCompiler.fn->arity = fnSignature.arity; + + finishBody(&fnCompiler); + + // Name the function based on the method its passed to. + char blockName[MAX_METHOD_SIGNATURE + 15]; + int blockLength; + signatureToString(&called, blockName, &blockLength); + memmove(blockName + blockLength, " block argument", 16); + + endCompiler(&fnCompiler, blockName, blockLength + 15); + } + + // TODO: Allow Grace-style mixfix methods? + + // If this is a super() call for an initializer, make sure we got an actual + // argument list. + if (signature->type == SIG_INITIALIZER) { + if (called.type != SIG_METHOD) { + error(compiler, "A superclass constructor must have an argument list."); + } + + called.type = SIG_INITIALIZER; + } + + callSignature(compiler, instruction, &called); +} + +// Compiles a call whose name is the previously consumed token. This includes +// getters, method calls with arguments, and setter calls. +static void namedCall(Compiler* compiler, bool canAssign, Code instruction) { + // Get the token for the method name. + Signature signature = signatureFromToken(compiler, SIG_GETTER); + + if (canAssign && match(compiler, TOKEN_EQ)) { + ignoreNewlines(compiler); + + // Build the setter signature. + signature.type = SIG_SETTER; + signature.arity = 1; + + // Compile the assigned value. + expression(compiler); + callSignature(compiler, instruction, &signature); + } else { + methodCall(compiler, instruction, &signature); + allowLineBeforeDot(compiler); + } +} + +// Emits the code to load [variable] onto the stack. +static void loadVariable(Compiler* compiler, Variable variable) { + switch (variable.scope) { + case SCOPE_LOCAL: + loadLocal(compiler, variable.index); + break; + case SCOPE_UPVALUE: + emitByteArg(compiler, CODE_LOAD_UPVALUE, variable.index); + break; + case SCOPE_MODULE: + emitShortArg(compiler, CODE_LOAD_MODULE_VAR, variable.index); + break; + default: + UNREACHABLE(); + } +} + +// Loads the receiver of the currently enclosing method. Correctly handles +// functions defined inside methods. +static void loadThis(Compiler* compiler) { + loadVariable(compiler, resolveNonmodule(compiler, "this", 4)); +} + +// Pushes the value for a module-level variable implicitly imported from core. +static void loadCoreVariable(Compiler* compiler, const char* name) { + int symbol = wrenSymbolTableFind(&compiler->parser->module->variableNames, name, strlen(name)); + ASSERT(symbol != -1, "Should have already defined core name."); + emitShortArg(compiler, CODE_LOAD_MODULE_VAR, symbol); +} + +// A parenthesized expression. +static void grouping(Compiler* compiler, bool canAssign) { + expression(compiler); + consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after expression."); +} + +// A list literal. +static void list(Compiler* compiler, bool canAssign) { + // Instantiate a new list. + loadCoreVariable(compiler, "List"); + callMethod(compiler, 0, "new()", 5); + + // Compile the list elements. Each one compiles to a ".add()" call. + do { + ignoreNewlines(compiler); + + // Stop if we hit the end of the list. + if (peek(compiler) == TOKEN_RIGHT_BRACKET) break; + + // The element. + expression(compiler); + callMethod(compiler, 1, "addCore_(_)", 11); + } while (match(compiler, TOKEN_COMMA)); + + // Allow newlines before the closing ']'. + ignoreNewlines(compiler); + consume(compiler, TOKEN_RIGHT_BRACKET, "Expect ']' after list elements."); +} + +// A map literal. +static void map(Compiler* compiler, bool canAssign) { + // Instantiate a new map. + loadCoreVariable(compiler, "Map"); + callMethod(compiler, 0, "new()", 5); + + // Compile the map elements. Each one is compiled to just invoke the + // subscript setter on the map. + do { + ignoreNewlines(compiler); + + // Stop if we hit the end of the map. + if (peek(compiler) == TOKEN_RIGHT_BRACE) break; + + // The key. + parsePrecedence(compiler, PREC_UNARY); + consume(compiler, TOKEN_COLON, "Expect ':' after map key."); + ignoreNewlines(compiler); + + // The value. + expression(compiler); + callMethod(compiler, 2, "addCore_(_,_)", 13); + } while (match(compiler, TOKEN_COMMA)); + + // Allow newlines before the closing '}'. + ignoreNewlines(compiler); + consume(compiler, TOKEN_RIGHT_BRACE, "Expect '}' after map entries."); +} + +// Unary operators like `-foo`. +static void unaryOp(Compiler* compiler, bool canAssign) { + GrammarRule* rule = getRule(compiler->parser->previous.type); + + ignoreNewlines(compiler); + + // Compile the argument. + parsePrecedence(compiler, (Precedence)(PREC_UNARY + 1)); + + // Call the operator method on the left-hand side. + callMethod(compiler, 0, rule->name, 1); +} + +static void boolean(Compiler* compiler, bool canAssign) { + emitOp(compiler, compiler->parser->previous.type == TOKEN_FALSE ? CODE_FALSE : CODE_TRUE); +} + +// Walks the compiler chain to find the compiler for the nearest class +// enclosing this one. Returns NULL if not currently inside a class definition. +static Compiler* getEnclosingClassCompiler(Compiler* compiler) { + while (compiler != NULL) { + if (compiler->enclosingClass != NULL) return compiler; + compiler = compiler->parent; + } + + return NULL; +} + +// Walks the compiler chain to find the nearest class enclosing this one. +// Returns NULL if not currently inside a class definition. +static ClassInfo* getEnclosingClass(Compiler* compiler) { + compiler = getEnclosingClassCompiler(compiler); + return compiler == NULL ? NULL : compiler->enclosingClass; +} + +static void field(Compiler* compiler, bool canAssign) { + // Initialize it with a fake value so we can keep parsing and minimize the + // number of cascaded errors. + int field = MAX_FIELDS; + + ClassInfo* enclosingClass = getEnclosingClass(compiler); + + if (enclosingClass == NULL) { + error(compiler, "Cannot reference a field outside of a class definition."); + } else if (enclosingClass->isForeign) { + error(compiler, "Cannot define fields in a foreign class."); + } else if (enclosingClass->inStatic) { + error(compiler, "Cannot use an instance field in a static method."); + } else { + // Look up the field, or implicitly define it. + field = wrenSymbolTableEnsure(compiler->parser->vm, &enclosingClass->fields, compiler->parser->previous.start, + compiler->parser->previous.length); + + if (field >= MAX_FIELDS) { + error(compiler, "A class can only have %d fields.", MAX_FIELDS); + } + } + + // If there's an "=" after a field name, it's an assignment. + bool isLoad = true; + if (canAssign && match(compiler, TOKEN_EQ)) { + // Compile the right-hand side. + expression(compiler); + isLoad = false; + } + + // If we're directly inside a method, use a more optimal instruction. + if (compiler->parent != NULL && compiler->parent->enclosingClass == enclosingClass) { + emitByteArg(compiler, isLoad ? CODE_LOAD_FIELD_THIS : CODE_STORE_FIELD_THIS, field); + } else { + loadThis(compiler); + emitByteArg(compiler, isLoad ? CODE_LOAD_FIELD : CODE_STORE_FIELD, field); + } + + allowLineBeforeDot(compiler); +} + +// Compiles a read or assignment to [variable]. +static void bareName(Compiler* compiler, bool canAssign, Variable variable) { + // If there's an "=" after a bare name, it's a variable assignment. + if (canAssign && match(compiler, TOKEN_EQ)) { + // Compile the right-hand side. + expression(compiler); + + // Emit the store instruction. + switch (variable.scope) { + case SCOPE_LOCAL: + emitByteArg(compiler, CODE_STORE_LOCAL, variable.index); + break; + case SCOPE_UPVALUE: + emitByteArg(compiler, CODE_STORE_UPVALUE, variable.index); + break; + case SCOPE_MODULE: + emitShortArg(compiler, CODE_STORE_MODULE_VAR, variable.index); + break; + default: + UNREACHABLE(); + } + return; + } + + // Emit the load instruction. + loadVariable(compiler, variable); + + allowLineBeforeDot(compiler); +} + +static void staticField(Compiler* compiler, bool canAssign) { + Compiler* classCompiler = getEnclosingClassCompiler(compiler); + if (classCompiler == NULL) { + error(compiler, "Cannot use a static field outside of a class definition."); + return; + } + + // Look up the name in the scope chain. + Token* token = &compiler->parser->previous; + + // If this is the first time we've seen this static field, implicitly + // define it as a variable in the scope surrounding the class definition. + if (resolveLocal(classCompiler, token->start, token->length) == -1) { + int symbol = declareVariable(classCompiler, NULL); + + // Implicitly initialize it to null. + emitOp(classCompiler, CODE_NULL); + defineVariable(classCompiler, symbol); + } + + // It definitely exists now, so resolve it properly. This is different from + // the above resolveLocal() call because we may have already closed over it + // as an upvalue. + Variable variable = resolveName(compiler, token->start, token->length); + bareName(compiler, canAssign, variable); +} + +// Compiles a variable name or method call with an implicit receiver. +static void name(Compiler* compiler, bool canAssign) { + // Look for the name in the scope chain up to the nearest enclosing method. + Token* token = &compiler->parser->previous; + + Variable variable = resolveNonmodule(compiler, token->start, token->length); + if (variable.index != -1) { + bareName(compiler, canAssign, variable); + return; + } + + // TODO: The fact that we return above here if the variable is known and parse + // an optional argument list below if not means that the grammar is not + // context-free. A line of code in a method like "someName(foo)" is a parse + // error if "someName" is a defined variable in the surrounding scope and not + // if it isn't. Fix this. One option is to have "someName(foo)" always + // resolve to a self-call if there is an argument list, but that makes + // getters a little confusing. + + // If we're inside a method and the name is lowercase, treat it as a method + // on this. + if (wrenIsLocalName(token->start) && getEnclosingClass(compiler) != NULL) { + loadThis(compiler); + namedCall(compiler, canAssign, CODE_CALL_0); + return; + } + + // Otherwise, look for a module-level variable with the name. + variable.scope = SCOPE_MODULE; + variable.index = wrenSymbolTableFind(&compiler->parser->module->variableNames, token->start, token->length); + if (variable.index == -1) { + // Implicitly define a module-level variable in + // the hopes that we get a real definition later. + variable.index = wrenDeclareVariable(compiler->parser->vm, compiler->parser->module, token->start, + token->length, token->line); + + if (variable.index == -2) { + error(compiler, "Too many module variables defined."); + } + } + + bareName(compiler, canAssign, variable); +} + +static void null(Compiler* compiler, bool canAssign) { + emitOp(compiler, CODE_NULL); +} + +// A number or string literal. +static void literal(Compiler* compiler, bool canAssign) { + emitConstant(compiler, compiler->parser->previous.value); +} + +// A string literal that contains interpolated expressions. +// +// Interpolation is syntactic sugar for calling ".join()" on a list. So the +// string: +// +// "a %(b + c) d" +// +// is compiled roughly like: +// +// ["a ", b + c, " d"].join() +static void stringInterpolation(Compiler* compiler, bool canAssign) { + // Instantiate a new list. + loadCoreVariable(compiler, "List"); + callMethod(compiler, 0, "new()", 5); + + do { + // The opening string part. + literal(compiler, false); + callMethod(compiler, 1, "addCore_(_)", 11); + + // The interpolated expression. + ignoreNewlines(compiler); + expression(compiler); + callMethod(compiler, 1, "addCore_(_)", 11); + + ignoreNewlines(compiler); + } while (match(compiler, TOKEN_INTERPOLATION)); + + // The trailing string part. + consume(compiler, TOKEN_STRING, "Expect end of string interpolation."); + literal(compiler, false); + callMethod(compiler, 1, "addCore_(_)", 11); + + // The list of interpolated parts. + callMethod(compiler, 0, "join()", 6); +} + +static void super_(Compiler* compiler, bool canAssign) { + ClassInfo* enclosingClass = getEnclosingClass(compiler); + if (enclosingClass == NULL) { + error(compiler, "Cannot use 'super' outside of a method."); + } + + loadThis(compiler); + + // TODO: Super operator calls. + // TODO: There's no syntax for invoking a superclass constructor with a + // different name from the enclosing one. Figure that out. + + // See if it's a named super call, or an unnamed one. + if (match(compiler, TOKEN_DOT)) { + // Compile the superclass call. + consume(compiler, TOKEN_NAME, "Expect method name after 'super.'."); + namedCall(compiler, canAssign, CODE_SUPER_0); + } else if (enclosingClass != NULL) { + // No explicit name, so use the name of the enclosing method. Make sure we + // check that enclosingClass isn't NULL first. We've already reported the + // error, but we don't want to crash here. + methodCall(compiler, CODE_SUPER_0, enclosingClass->signature); + } +} + +static void this_(Compiler* compiler, bool canAssign) { + if (getEnclosingClass(compiler) == NULL) { + error(compiler, "Cannot use 'this' outside of a method."); + return; + } + + loadThis(compiler); +} + +// Subscript or "array indexing" operator like `foo[bar]`. +static void subscript(Compiler* compiler, bool canAssign) { + Signature signature = {"", 0, SIG_SUBSCRIPT, 0}; + + // Parse the argument list. + finishArgumentList(compiler, &signature); + consume(compiler, TOKEN_RIGHT_BRACKET, "Expect ']' after arguments."); + + allowLineBeforeDot(compiler); + + if (canAssign && match(compiler, TOKEN_EQ)) { + signature.type = SIG_SUBSCRIPT_SETTER; + + // Compile the assigned value. + validateNumParameters(compiler, ++signature.arity); + expression(compiler); + } + + callSignature(compiler, CODE_CALL_0, &signature); +} + +static void call(Compiler* compiler, bool canAssign) { + ignoreNewlines(compiler); + consume(compiler, TOKEN_NAME, "Expect method name after '.'."); + namedCall(compiler, canAssign, CODE_CALL_0); +} + +static void and_(Compiler* compiler, bool canAssign) { + ignoreNewlines(compiler); + + // Skip the right argument if the left is false. + int jump = emitJump(compiler, CODE_AND); + parsePrecedence(compiler, PREC_LOGICAL_AND); + patchJump(compiler, jump); +} + +static void or_(Compiler* compiler, bool canAssign) { + ignoreNewlines(compiler); + + // Skip the right argument if the left is true. + int jump = emitJump(compiler, CODE_OR); + parsePrecedence(compiler, PREC_LOGICAL_OR); + patchJump(compiler, jump); +} + +static void conditional(Compiler* compiler, bool canAssign) { + // Ignore newline after '?'. + ignoreNewlines(compiler); + + // Jump to the else branch if the condition is false. + int ifJump = emitJump(compiler, CODE_JUMP_IF); + + // Compile the then branch. + parsePrecedence(compiler, PREC_CONDITIONAL); + + consume(compiler, TOKEN_COLON, "Expect ':' after then branch of conditional operator."); + ignoreNewlines(compiler); + + // Jump over the else branch when the if branch is taken. + int elseJump = emitJump(compiler, CODE_JUMP); + + // Compile the else branch. + patchJump(compiler, ifJump); + + parsePrecedence(compiler, PREC_ASSIGNMENT); + + // Patch the jump over the else. + patchJump(compiler, elseJump); +} + +void infixOp(Compiler* compiler, bool canAssign) { + GrammarRule* rule = getRule(compiler->parser->previous.type); + + // An infix operator cannot end an expression. + ignoreNewlines(compiler); + + // Compile the right-hand side. + parsePrecedence(compiler, (Precedence)(rule->precedence + 1)); + + // Call the operator method on the left-hand side. + Signature signature = {rule->name, (int)strlen(rule->name), SIG_METHOD, 1}; + callSignature(compiler, CODE_CALL_0, &signature); +} + +// Compiles a method signature for an infix operator. +void infixSignature(Compiler* compiler, Signature* signature) { + // Add the RHS parameter. + signature->type = SIG_METHOD; + signature->arity = 1; + + // Parse the parameter name. + consume(compiler, TOKEN_LEFT_PAREN, "Expect '(' after operator name."); + declareNamedVariable(compiler); + consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after parameter name."); +} + +// Compiles a method signature for an unary operator (i.e. "!"). +void unarySignature(Compiler* compiler, Signature* signature) { + // Do nothing. The name is already complete. + signature->type = SIG_GETTER; +} + +// Compiles a method signature for an operator that can either be unary or +// infix (i.e. "-"). +void mixedSignature(Compiler* compiler, Signature* signature) { + signature->type = SIG_GETTER; + + // If there is a parameter, it's an infix operator, otherwise it's unary. + if (match(compiler, TOKEN_LEFT_PAREN)) { + // Add the RHS parameter. + signature->type = SIG_METHOD; + signature->arity = 1; + + // Parse the parameter name. + declareNamedVariable(compiler); + consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after parameter name."); + } +} + +// Compiles an optional setter parameter in a method [signature]. +// +// Returns `true` if it was a setter. +static bool maybeSetter(Compiler* compiler, Signature* signature) { + // See if it's a setter. + if (!match(compiler, TOKEN_EQ)) return false; + + // It's a setter. + if (signature->type == SIG_SUBSCRIPT) { + signature->type = SIG_SUBSCRIPT_SETTER; + } else { + signature->type = SIG_SETTER; + } + + // Parse the value parameter. + consume(compiler, TOKEN_LEFT_PAREN, "Expect '(' after '='."); + declareNamedVariable(compiler); + consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after parameter name."); + + signature->arity++; + + return true; +} + +// Compiles a method signature for a subscript operator. +void subscriptSignature(Compiler* compiler, Signature* signature) { + signature->type = SIG_SUBSCRIPT; + + // The signature currently has "[" as its name since that was the token that + // matched it. Clear that out. + signature->length = 0; + + // Parse the parameters inside the subscript. + finishParameterList(compiler, signature); + consume(compiler, TOKEN_RIGHT_BRACKET, "Expect ']' after parameters."); + + maybeSetter(compiler, signature); +} + +// Parses an optional parenthesized parameter list. Updates `type` and `arity` +// in [signature] to match what was parsed. +static void parameterList(Compiler* compiler, Signature* signature) { + // The parameter list is optional. + if (!match(compiler, TOKEN_LEFT_PAREN)) return; + + signature->type = SIG_METHOD; + + // Allow new line before an empty argument list + ignoreNewlines(compiler); + + // Allow an empty parameter list. + if (match(compiler, TOKEN_RIGHT_PAREN)) return; + + finishParameterList(compiler, signature); + consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after parameters."); +} + +// Compiles a method signature for a named method or setter. +void namedSignature(Compiler* compiler, Signature* signature) { + signature->type = SIG_GETTER; + + // If it's a setter, it can't also have a parameter list. + if (maybeSetter(compiler, signature)) return; + + // Regular named method with an optional parameter list. + parameterList(compiler, signature); +} + +// Compiles a method signature for a constructor. +void constructorSignature(Compiler* compiler, Signature* signature) { + consume(compiler, TOKEN_NAME, "Expect constructor name after 'construct'."); + + // Capture the name. + *signature = signatureFromToken(compiler, SIG_INITIALIZER); + + if (match(compiler, TOKEN_EQ)) { + error(compiler, "A constructor cannot be a setter."); + } + + if (!match(compiler, TOKEN_LEFT_PAREN)) { + error(compiler, "A constructor cannot be a getter."); + return; + } + + // Allow an empty parameter list. + if (match(compiler, TOKEN_RIGHT_PAREN)) return; + + finishParameterList(compiler, signature); + consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after parameters."); +} + +// This table defines all of the parsing rules for the prefix and infix +// expressions in the grammar. Expressions are parsed using a Pratt parser. +// +// See: http://journal.stuffwithstuff.com/2011/03/19/pratt-parsers-expression-parsing-made-easy/ +#define UNUSED \ + { NULL, NULL, NULL, PREC_NONE, NULL } +#define PREFIX(fn) \ + { fn, NULL, NULL, PREC_NONE, NULL } +#define INFIX(prec, fn) \ + { NULL, fn, NULL, prec, NULL } +#define INFIX_OPERATOR(prec, name) \ + { NULL, infixOp, infixSignature, prec, name } +#define PREFIX_OPERATOR(name) \ + { unaryOp, NULL, unarySignature, PREC_NONE, name } +#define OPERATOR(name) \ + { unaryOp, infixOp, mixedSignature, PREC_TERM, name } + +GrammarRule rules[] = { + /* TOKEN_LEFT_PAREN */ PREFIX(grouping), + /* TOKEN_RIGHT_PAREN */ UNUSED, + /* TOKEN_LEFT_BRACKET */ {list, subscript, subscriptSignature, PREC_CALL, NULL}, + /* TOKEN_RIGHT_BRACKET */ UNUSED, + /* TOKEN_LEFT_BRACE */ PREFIX(map), + /* TOKEN_RIGHT_BRACE */ UNUSED, + /* TOKEN_COLON */ UNUSED, + /* TOKEN_DOT */ INFIX(PREC_CALL, call), + /* TOKEN_DOTDOT */ INFIX_OPERATOR(PREC_RANGE, ".."), + /* TOKEN_DOTDOTDOT */ INFIX_OPERATOR(PREC_RANGE, "..."), + /* TOKEN_COMMA */ UNUSED, + /* TOKEN_STAR */ INFIX_OPERATOR(PREC_FACTOR, "*"), + /* TOKEN_SLASH */ INFIX_OPERATOR(PREC_FACTOR, "/"), + /* TOKEN_PERCENT */ INFIX_OPERATOR(PREC_FACTOR, "%"), + /* TOKEN_HASH */ UNUSED, + /* TOKEN_PLUS */ INFIX_OPERATOR(PREC_TERM, "+"), + /* TOKEN_MINUS */ OPERATOR("-"), + /* TOKEN_LTLT */ INFIX_OPERATOR(PREC_BITWISE_SHIFT, "<<"), + /* TOKEN_GTGT */ INFIX_OPERATOR(PREC_BITWISE_SHIFT, ">>"), + /* TOKEN_PIPE */ INFIX_OPERATOR(PREC_BITWISE_OR, "|"), + /* TOKEN_PIPEPIPE */ INFIX(PREC_LOGICAL_OR, or_), + /* TOKEN_CARET */ INFIX_OPERATOR(PREC_BITWISE_XOR, "^"), + /* TOKEN_AMP */ INFIX_OPERATOR(PREC_BITWISE_AND, "&"), + /* TOKEN_AMPAMP */ INFIX(PREC_LOGICAL_AND, and_), + /* TOKEN_BANG */ PREFIX_OPERATOR("!"), + /* TOKEN_TILDE */ PREFIX_OPERATOR("~"), + /* TOKEN_QUESTION */ INFIX(PREC_ASSIGNMENT, conditional), + /* TOKEN_EQ */ UNUSED, + /* TOKEN_LT */ INFIX_OPERATOR(PREC_COMPARISON, "<"), + /* TOKEN_GT */ INFIX_OPERATOR(PREC_COMPARISON, ">"), + /* TOKEN_LTEQ */ INFIX_OPERATOR(PREC_COMPARISON, "<="), + /* TOKEN_GTEQ */ INFIX_OPERATOR(PREC_COMPARISON, ">="), + /* TOKEN_EQEQ */ INFIX_OPERATOR(PREC_EQUALITY, "=="), + /* TOKEN_BANGEQ */ INFIX_OPERATOR(PREC_EQUALITY, "!="), + /* TOKEN_BREAK */ UNUSED, + /* TOKEN_CONTINUE */ UNUSED, + /* TOKEN_CLASS */ UNUSED, + /* TOKEN_CONSTRUCT */ {NULL, NULL, constructorSignature, PREC_NONE, NULL}, + /* TOKEN_ELSE */ UNUSED, + /* TOKEN_FALSE */ PREFIX(boolean), + /* TOKEN_FOR */ UNUSED, + /* TOKEN_FOREIGN */ UNUSED, + /* TOKEN_IF */ UNUSED, + /* TOKEN_IMPORT */ UNUSED, + /* TOKEN_AS */ UNUSED, + /* TOKEN_IN */ UNUSED, + /* TOKEN_IS */ INFIX_OPERATOR(PREC_IS, "is"), + /* TOKEN_NULL */ PREFIX(null), + /* TOKEN_RETURN */ UNUSED, + /* TOKEN_STATIC */ UNUSED, + /* TOKEN_SUPER */ PREFIX(super_), + /* TOKEN_THIS */ PREFIX(this_), + /* TOKEN_TRUE */ PREFIX(boolean), + /* TOKEN_VAR */ UNUSED, + /* TOKEN_WHILE */ UNUSED, + /* TOKEN_FIELD */ PREFIX(field), + /* TOKEN_STATIC_FIELD */ PREFIX(staticField), + /* TOKEN_NAME */ {name, NULL, namedSignature, PREC_NONE, NULL}, + /* TOKEN_NUMBER */ PREFIX(literal), + /* TOKEN_STRING */ PREFIX(literal), + /* TOKEN_INTERPOLATION */ PREFIX(stringInterpolation), + /* TOKEN_LINE */ UNUSED, + /* TOKEN_ERROR */ UNUSED, + /* TOKEN_EOF */ UNUSED}; + +// Gets the [GrammarRule] associated with tokens of [type]. +static GrammarRule* getRule(TokenType type) { + return &rules[type]; +} + +// The main entrypoint for the top-down operator precedence parser. +void parsePrecedence(Compiler* compiler, Precedence precedence) { + nextToken(compiler->parser); + GrammarFn prefix = rules[compiler->parser->previous.type].prefix; + + if (prefix == NULL) { + error(compiler, "Expected expression."); + return; + } + + // Track if the precendence of the surrounding expression is low enough to + // allow an assignment inside this one. We can't compile an assignment like + // a normal expression because it requires us to handle the LHS specially -- + // it needs to be an lvalue, not an rvalue. So, for each of the kinds of + // expressions that are valid lvalues -- names, subscripts, fields, etc. -- + // we pass in whether or not it appears in a context loose enough to allow + // "=". If so, it will parse the "=" itself and handle it appropriately. + bool canAssign = precedence <= PREC_CONDITIONAL; + prefix(compiler, canAssign); + + while (precedence <= rules[compiler->parser->current.type].precedence) { + nextToken(compiler->parser); + GrammarFn infix = rules[compiler->parser->previous.type].infix; + infix(compiler, canAssign); + } +} + +// Parses an expression. Unlike statements, expressions leave a resulting value +// on the stack. +void expression(Compiler* compiler) { + parsePrecedence(compiler, PREC_LOWEST); +} + +// Returns the number of bytes for the arguments to the instruction +// at [ip] in [fn]'s bytecode. +static int getByteCountForArguments(const uint8_t* bytecode, const Value* constants, int ip) { + Code instruction = (Code)bytecode[ip]; + switch (instruction) { + case CODE_NULL: + case CODE_FALSE: + case CODE_TRUE: + case CODE_POP: + case CODE_CLOSE_UPVALUE: + case CODE_RETURN: + case CODE_END: + case CODE_LOAD_LOCAL_0: + case CODE_LOAD_LOCAL_1: + case CODE_LOAD_LOCAL_2: + case CODE_LOAD_LOCAL_3: + case CODE_LOAD_LOCAL_4: + case CODE_LOAD_LOCAL_5: + case CODE_LOAD_LOCAL_6: + case CODE_LOAD_LOCAL_7: + case CODE_LOAD_LOCAL_8: + case CODE_CONSTRUCT: + case CODE_FOREIGN_CONSTRUCT: + case CODE_FOREIGN_CLASS: + case CODE_END_MODULE: + case CODE_END_CLASS: + return 0; + + case CODE_LOAD_LOCAL: + case CODE_STORE_LOCAL: + case CODE_LOAD_UPVALUE: + case CODE_STORE_UPVALUE: + case CODE_LOAD_FIELD_THIS: + case CODE_STORE_FIELD_THIS: + case CODE_LOAD_FIELD: + case CODE_STORE_FIELD: + case CODE_CLASS: + return 1; + + case CODE_CONSTANT: + case CODE_LOAD_MODULE_VAR: + case CODE_STORE_MODULE_VAR: + case CODE_CALL_0: + case CODE_CALL_1: + case CODE_CALL_2: + case CODE_CALL_3: + case CODE_CALL_4: + case CODE_CALL_5: + case CODE_CALL_6: + case CODE_CALL_7: + case CODE_CALL_8: + case CODE_CALL_9: + case CODE_CALL_10: + case CODE_CALL_11: + case CODE_CALL_12: + case CODE_CALL_13: + case CODE_CALL_14: + case CODE_CALL_15: + case CODE_CALL_16: + case CODE_JUMP: + case CODE_LOOP: + case CODE_JUMP_IF: + case CODE_AND: + case CODE_OR: + case CODE_METHOD_INSTANCE: + case CODE_METHOD_STATIC: + case CODE_IMPORT_MODULE: + case CODE_IMPORT_VARIABLE: + return 2; + + case CODE_SUPER_0: + case CODE_SUPER_1: + case CODE_SUPER_2: + case CODE_SUPER_3: + case CODE_SUPER_4: + case CODE_SUPER_5: + case CODE_SUPER_6: + case CODE_SUPER_7: + case CODE_SUPER_8: + case CODE_SUPER_9: + case CODE_SUPER_10: + case CODE_SUPER_11: + case CODE_SUPER_12: + case CODE_SUPER_13: + case CODE_SUPER_14: + case CODE_SUPER_15: + case CODE_SUPER_16: + return 4; + + case CODE_CLOSURE: { + int constant = (bytecode[ip + 1] << 8) | bytecode[ip + 2]; + ObjFn* loadedFn = AS_FN(constants[constant]); + + // There are two bytes for the constant, then two for each upvalue. + return 2 + (loadedFn->numUpvalues * 2); + } + } + + UNREACHABLE(); + return 0; +} + +// Marks the beginning of a loop. Keeps track of the current instruction so we +// know what to loop back to at the end of the body. +static void startLoop(Compiler* compiler, Loop* loop) { + loop->enclosing = compiler->loop; + loop->start = compiler->fn->code.count - 1; + loop->scopeDepth = compiler->scopeDepth; + compiler->loop = loop; +} + +// Emits the [CODE_JUMP_IF] instruction used to test the loop condition and +// potentially exit the loop. Keeps track of the instruction so we can patch it +// later once we know where the end of the body is. +static void testExitLoop(Compiler* compiler) { + compiler->loop->exitJump = emitJump(compiler, CODE_JUMP_IF); +} + +// Compiles the body of the loop and tracks its extent so that contained "break" +// statements can be handled correctly. +static void loopBody(Compiler* compiler) { + compiler->loop->body = compiler->fn->code.count; + statement(compiler); +} + +// Ends the current innermost loop. Patches up all jumps and breaks now that +// we know where the end of the loop is. +static void endLoop(Compiler* compiler) { + // We don't check for overflow here since the forward jump over the loop body + // will report an error for the same problem. + int loopOffset = compiler->fn->code.count - compiler->loop->start + 2; + emitShortArg(compiler, CODE_LOOP, loopOffset); + + patchJump(compiler, compiler->loop->exitJump); + + // Find any break placeholder instructions (which will be CODE_END in the + // bytecode) and replace them with real jumps. + int i = compiler->loop->body; + while (i < compiler->fn->code.count) { + if (compiler->fn->code.data[i] == CODE_END) { + compiler->fn->code.data[i] = CODE_JUMP; + patchJump(compiler, i + 1); + i += 3; + } else { + // Skip this instruction and its arguments. + i += 1 + getByteCountForArguments(compiler->fn->code.data, compiler->fn->constants.data, i); + } + } + + compiler->loop = compiler->loop->enclosing; +} + +static void forStatement(Compiler* compiler) { + // A for statement like: + // + // for (i in sequence.expression) { + // System.print(i) + // } + // + // Is compiled to bytecode almost as if the source looked like this: + // + // { + // var seq_ = sequence.expression + // var iter_ + // while (iter_ = seq_.iterate(iter_)) { + // var i = seq_.iteratorValue(iter_) + // System.print(i) + // } + // } + // + // It's not exactly this, because the synthetic variables `seq_` and `iter_` + // actually get names that aren't valid Wren identfiers, but that's the basic + // idea. + // + // The important parts are: + // - The sequence expression is only evaluated once. + // - The .iterate() method is used to advance the iterator and determine if + // it should exit the loop. + // - The .iteratorValue() method is used to get the value at the current + // iterator position. + + // Create a scope for the hidden local variables used for the iterator. + pushScope(compiler); + + consume(compiler, TOKEN_LEFT_PAREN, "Expect '(' after 'for'."); + consume(compiler, TOKEN_NAME, "Expect for loop variable name."); + + // Remember the name of the loop variable. + const char* name = compiler->parser->previous.start; + int length = compiler->parser->previous.length; + + consume(compiler, TOKEN_IN, "Expect 'in' after loop variable."); + ignoreNewlines(compiler); + + // Evaluate the sequence expression and store it in a hidden local variable. + // The space in the variable name ensures it won't collide with a user-defined + // variable. + expression(compiler); + + // Verify that there is space to hidden local variables. + // Note that we expect only two addLocal calls next to each other in the + // following code. + if (compiler->numLocals + 2 > MAX_LOCALS) { + error(compiler, + "Cannot declare more than %d variables in one scope. (Not enough space for for-loops internal variables)", + MAX_LOCALS); + return; + } + int seqSlot = addLocal(compiler, "seq ", 4); + + // Create another hidden local for the iterator object. + null(compiler, false); + int iterSlot = addLocal(compiler, "iter ", 5); + + consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after loop expression."); + + Loop loop; + startLoop(compiler, &loop); + + // Advance the iterator by calling the ".iterate" method on the sequence. + loadLocal(compiler, seqSlot); + loadLocal(compiler, iterSlot); + + // Update and test the iterator. + callMethod(compiler, 1, "iterate(_)", 10); + emitByteArg(compiler, CODE_STORE_LOCAL, iterSlot); + testExitLoop(compiler); + + // Get the current value in the sequence by calling ".iteratorValue". + loadLocal(compiler, seqSlot); + loadLocal(compiler, iterSlot); + callMethod(compiler, 1, "iteratorValue(_)", 16); + + // Bind the loop variable in its own scope. This ensures we get a fresh + // variable each iteration so that closures for it don't all see the same one. + pushScope(compiler); + addLocal(compiler, name, length); + + loopBody(compiler); + + // Loop variable. + popScope(compiler); + + endLoop(compiler); + + // Hidden variables. + popScope(compiler); +} + +static void ifStatement(Compiler* compiler) { + // Compile the condition. + consume(compiler, TOKEN_LEFT_PAREN, "Expect '(' after 'if'."); + expression(compiler); + consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after if condition."); + + // Jump to the else branch if the condition is false. + int ifJump = emitJump(compiler, CODE_JUMP_IF); + + // Compile the then branch. + statement(compiler); + + // Compile the else branch if there is one. + if (match(compiler, TOKEN_ELSE)) { + // Jump over the else branch when the if branch is taken. + int elseJump = emitJump(compiler, CODE_JUMP); + patchJump(compiler, ifJump); + + statement(compiler); + + // Patch the jump over the else. + patchJump(compiler, elseJump); + } else { + patchJump(compiler, ifJump); + } +} + +static void whileStatement(Compiler* compiler) { + Loop loop; + startLoop(compiler, &loop); + + // Compile the condition. + consume(compiler, TOKEN_LEFT_PAREN, "Expect '(' after 'while'."); + expression(compiler); + consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after while condition."); + + testExitLoop(compiler); + loopBody(compiler); + endLoop(compiler); +} + +// Compiles a simple statement. These can only appear at the top-level or +// within curly blocks. Simple statements exclude variable binding statements +// like "var" and "class" which are not allowed directly in places like the +// branches of an "if" statement. +// +// Unlike expressions, statements do not leave a value on the stack. +void statement(Compiler* compiler) { + if (match(compiler, TOKEN_BREAK)) { + if (compiler->loop == NULL) { + error(compiler, "Cannot use 'break' outside of a loop."); + return; + } + + // Since we will be jumping out of the scope, make sure any locals in it + // are discarded first. + discardLocals(compiler, compiler->loop->scopeDepth + 1); + + // Emit a placeholder instruction for the jump to the end of the body. When + // we're done compiling the loop body and know where the end is, we'll + // replace these with `CODE_JUMP` instructions with appropriate offsets. + // We use `CODE_END` here because that can't occur in the middle of + // bytecode. + emitJump(compiler, CODE_END); + } else if (match(compiler, TOKEN_CONTINUE)) { + if (compiler->loop == NULL) { + error(compiler, "Cannot use 'continue' outside of a loop."); + return; + } + + // Since we will be jumping out of the scope, make sure any locals in it + // are discarded first. + discardLocals(compiler, compiler->loop->scopeDepth + 1); + + // emit a jump back to the top of the loop + int loopOffset = compiler->fn->code.count - compiler->loop->start + 2; + emitShortArg(compiler, CODE_LOOP, loopOffset); + } else if (match(compiler, TOKEN_FOR)) { + forStatement(compiler); + } else if (match(compiler, TOKEN_IF)) { + ifStatement(compiler); + } else if (match(compiler, TOKEN_RETURN)) { + // Compile the return value. + if (peek(compiler) == TOKEN_LINE) { + // If there's no expression after return, initializers should + // return 'this' and regular methods should return null + Code result = compiler->isInitializer ? CODE_LOAD_LOCAL_0 : CODE_NULL; + emitOp(compiler, result); + } else { + if (compiler->isInitializer) { + error(compiler, "A constructor cannot return a value."); + } + + expression(compiler); + } + + emitOp(compiler, CODE_RETURN); + } else if (match(compiler, TOKEN_WHILE)) { + whileStatement(compiler); + } else if (match(compiler, TOKEN_LEFT_BRACE)) { + // Block statement. + pushScope(compiler); + if (finishBlock(compiler)) { + // Block was an expression, so discard it. + emitOp(compiler, CODE_POP); + } + popScope(compiler); + } else { + // Expression statement. + expression(compiler); + emitOp(compiler, CODE_POP); + } +} + +// Creates a matching constructor method for an initializer with [signature] +// and [initializerSymbol]. +// +// Construction is a two-stage process in Wren that involves two separate +// methods. There is a static method that allocates a new instance of the class. +// It then invokes an initializer method on the new instance, forwarding all of +// the constructor arguments to it. +// +// The allocator method always has a fixed implementation: +// +// CODE_CONSTRUCT - Replace the class in slot 0 with a new instance of it. +// CODE_CALL - Invoke the initializer on the new instance. +// +// This creates that method and calls the initializer with [initializerSymbol]. +static void createConstructor(Compiler* compiler, Signature* signature, int initializerSymbol) { + Compiler methodCompiler; + initCompiler(&methodCompiler, compiler->parser, compiler, true); + + // Allocate the instance. + emitOp(&methodCompiler, compiler->enclosingClass->isForeign ? CODE_FOREIGN_CONSTRUCT : CODE_CONSTRUCT); + + // Run its initializer. + emitShortArg(&methodCompiler, (Code)(CODE_CALL_0 + signature->arity), initializerSymbol); + + // Return the instance. + emitOp(&methodCompiler, CODE_RETURN); + + endCompiler(&methodCompiler, "", 0); +} + +// Loads the enclosing class onto the stack and then binds the function already +// on the stack as a method on that class. +static void defineMethod(Compiler* compiler, Variable classVariable, bool isStatic, int methodSymbol) { + // Load the class. We have to do this for each method because we can't + // keep the class on top of the stack. If there are static fields, they + // will be locals above the initial variable slot for the class on the + // stack. To skip past those, we just load the class each time right before + // defining a method. + loadVariable(compiler, classVariable); + + // Define the method. + Code instruction = isStatic ? CODE_METHOD_STATIC : CODE_METHOD_INSTANCE; + emitShortArg(compiler, instruction, methodSymbol); +} + +// Declares a method in the enclosing class with [signature]. +// +// Reports an error if a method with that signature is already declared. +// Returns the symbol for the method. +static int declareMethod(Compiler* compiler, Signature* signature, const char* name, int length) { + int symbol = signatureSymbol(compiler, signature); + + // See if the class has already declared method with this signature. + ClassInfo* classInfo = compiler->enclosingClass; + IntBuffer* methods = classInfo->inStatic ? &classInfo->staticMethods : &classInfo->methods; + for (int i = 0; i < methods->count; i++) { + if (methods->data[i] == symbol) { + const char* staticPrefix = classInfo->inStatic ? "static " : ""; + error(compiler, "Class %s already defines a %smethod '%s'.", &compiler->enclosingClass->name->value, + staticPrefix, name); + break; + } + } + + wrenIntBufferWrite(compiler->parser->vm, methods, symbol); + return symbol; +} + +static Value consumeLiteral(Compiler* compiler, const char* message) { + if (match(compiler, TOKEN_FALSE)) return FALSE_VAL; + if (match(compiler, TOKEN_TRUE)) return TRUE_VAL; + if (match(compiler, TOKEN_NUMBER)) return compiler->parser->previous.value; + if (match(compiler, TOKEN_STRING)) return compiler->parser->previous.value; + if (match(compiler, TOKEN_NAME)) return compiler->parser->previous.value; + + error(compiler, message); + nextToken(compiler->parser); + return NULL_VAL; +} + +static bool matchAttribute(Compiler* compiler) { + if (match(compiler, TOKEN_HASH)) { + compiler->numAttributes++; + bool runtimeAccess = match(compiler, TOKEN_BANG); + if (match(compiler, TOKEN_NAME)) { + Value group = compiler->parser->previous.value; + TokenType ahead = peek(compiler); + if (ahead == TOKEN_EQ || ahead == TOKEN_LINE) { + Value key = group; + Value value = NULL_VAL; + if (match(compiler, TOKEN_EQ)) { + value = consumeLiteral(compiler, + "Expect a Bool, Num, String or Identifier literal for an attribute value."); + } + if (runtimeAccess) addToAttributeGroup(compiler, NULL_VAL, key, value); + } else if (match(compiler, TOKEN_LEFT_PAREN)) { + ignoreNewlines(compiler); + if (match(compiler, TOKEN_RIGHT_PAREN)) { + error(compiler, "Expected attributes in group, group cannot be empty."); + } else { + while (peek(compiler) != TOKEN_RIGHT_PAREN) { + consume(compiler, TOKEN_NAME, "Expect name for attribute key."); + Value key = compiler->parser->previous.value; + Value value = NULL_VAL; + if (match(compiler, TOKEN_EQ)) { + value = consumeLiteral( + compiler, + "Expect a Bool, Num, String or Identifier literal for an attribute value."); + } + if (runtimeAccess) addToAttributeGroup(compiler, group, key, value); + ignoreNewlines(compiler); + if (!match(compiler, TOKEN_COMMA)) break; + ignoreNewlines(compiler); + } + + ignoreNewlines(compiler); + consume(compiler, TOKEN_RIGHT_PAREN, "Expected ')' after grouped attributes."); + } + } else { + error(compiler, "Expect an equal, newline or grouping after an attribute key."); + } + } else { + error(compiler, "Expect an attribute definition after #."); + } + + consumeLine(compiler, "Expect newline after attribute."); + return true; + } + + return false; +} + +// Compiles a method definition inside a class body. +// +// Returns `true` if it compiled successfully, or `false` if the method couldn't +// be parsed. +static bool method(Compiler* compiler, Variable classVariable) { + // Parse any attributes before the method and store them + if (matchAttribute(compiler)) { + return method(compiler, classVariable); + } + + // TODO: What about foreign constructors? + bool isForeign = match(compiler, TOKEN_FOREIGN); + bool isStatic = match(compiler, TOKEN_STATIC); + compiler->enclosingClass->inStatic = isStatic; + + SignatureFn signatureFn = rules[compiler->parser->current.type].method; + nextToken(compiler->parser); + + if (signatureFn == NULL) { + error(compiler, "Expect method definition."); + return false; + } + + // Build the method signature. + Signature signature = signatureFromToken(compiler, SIG_GETTER); + compiler->enclosingClass->signature = &signature; + + Compiler methodCompiler; + initCompiler(&methodCompiler, compiler->parser, compiler, true); + + // Compile the method signature. + signatureFn(&methodCompiler, &signature); + + methodCompiler.isInitializer = signature.type == SIG_INITIALIZER; + + if (isStatic && signature.type == SIG_INITIALIZER) { + error(compiler, "A constructor cannot be static."); + } + + // Include the full signature in debug messages in stack traces. + char fullSignature[MAX_METHOD_SIGNATURE]; + int length; + signatureToString(&signature, fullSignature, &length); + + // Copy any attributes the compiler collected into the enclosing class + copyMethodAttributes(compiler, isForeign, isStatic, fullSignature, length); + + // Check for duplicate methods. Doesn't matter that it's already been + // defined, error will discard bytecode anyway. + // Check if the method table already contains this symbol + int methodSymbol = declareMethod(compiler, &signature, fullSignature, length); + + if (isForeign) { + // Define a constant for the signature. + emitConstant(compiler, wrenNewStringLength(compiler->parser->vm, fullSignature, length)); + + // We don't need the function we started compiling in the parameter list + // any more. + methodCompiler.parser->vm->compiler = methodCompiler.parent; + } else { + consume(compiler, TOKEN_LEFT_BRACE, "Expect '{' to begin method body."); + finishBody(&methodCompiler); + endCompiler(&methodCompiler, fullSignature, length); + } + + // Define the method. For a constructor, this defines the instance + // initializer method. + defineMethod(compiler, classVariable, isStatic, methodSymbol); + + if (signature.type == SIG_INITIALIZER) { + // Also define a matching constructor method on the metaclass. + signature.type = SIG_METHOD; + int constructorSymbol = signatureSymbol(compiler, &signature); + + createConstructor(compiler, &signature, methodSymbol); + defineMethod(compiler, classVariable, true, constructorSymbol); + } + + return true; +} + +// Compiles a class definition. Assumes the "class" token has already been +// consumed (along with a possibly preceding "foreign" token). +static void classDefinition(Compiler* compiler, bool isForeign) { + // Create a variable to store the class in. + Variable classVariable; + classVariable.scope = compiler->scopeDepth == -1 ? SCOPE_MODULE : SCOPE_LOCAL; + classVariable.index = declareNamedVariable(compiler); + + // Create shared class name value + Value classNameString = wrenNewStringLength(compiler->parser->vm, compiler->parser->previous.start, + compiler->parser->previous.length); + + // Create class name string to track method duplicates + ObjString* className = AS_STRING(classNameString); + + // Make a string constant for the name. + emitConstant(compiler, classNameString); + + // Load the superclass (if there is one). + if (match(compiler, TOKEN_IS)) { + parsePrecedence(compiler, PREC_CALL); + } else { + // Implicitly inherit from Object. + loadCoreVariable(compiler, "Object"); + } + + // Store a placeholder for the number of fields argument. We don't know the + // count until we've compiled all the methods to see which fields are used. + int numFieldsInstruction = -1; + if (isForeign) { + emitOp(compiler, CODE_FOREIGN_CLASS); + } else { + numFieldsInstruction = emitByteArg(compiler, CODE_CLASS, 255); + } + + // Store it in its name. + defineVariable(compiler, classVariable.index); + + // Push a local variable scope. Static fields in a class body are hoisted out + // into local variables declared in this scope. Methods that use them will + // have upvalues referencing them. + pushScope(compiler); + + ClassInfo classInfo; + classInfo.isForeign = isForeign; + classInfo.name = className; + + // Allocate attribute maps if necessary. + // A method will allocate the methods one if needed + classInfo.classAttributes = compiler->attributes->count > 0 ? wrenNewMap(compiler->parser->vm) : NULL; + classInfo.methodAttributes = NULL; + // Copy any existing attributes into the class + copyAttributes(compiler, classInfo.classAttributes); + + // Set up a symbol table for the class's fields. We'll initially compile + // them to slots starting at zero. When the method is bound to the class, the + // bytecode will be adjusted by [wrenBindMethod] to take inherited fields + // into account. + wrenSymbolTableInit(&classInfo.fields); + + // Set up symbol buffers to track duplicate static and instance methods. + wrenIntBufferInit(&classInfo.methods); + wrenIntBufferInit(&classInfo.staticMethods); + compiler->enclosingClass = &classInfo; + + // Compile the method definitions. + consume(compiler, TOKEN_LEFT_BRACE, "Expect '{' after class declaration."); + matchLine(compiler); + + while (!match(compiler, TOKEN_RIGHT_BRACE)) { + if (!method(compiler, classVariable)) break; + + // Don't require a newline after the last definition. + if (match(compiler, TOKEN_RIGHT_BRACE)) break; + + consumeLine(compiler, "Expect newline after definition in class."); + } + + // If any attributes are present, + // instantiate a ClassAttributes instance for the class + // and send it over to CODE_END_CLASS + bool hasAttr = classInfo.classAttributes != NULL || classInfo.methodAttributes != NULL; + if (hasAttr) { + emitClassAttributes(compiler, &classInfo); + loadVariable(compiler, classVariable); + // At the moment, we don't have other uses for CODE_END_CLASS, + // so we put it inside this condition. Later, we can always + // emit it and use it as needed. + emitOp(compiler, CODE_END_CLASS); + } + + // Update the class with the number of fields. + if (!isForeign) { + compiler->fn->code.data[numFieldsInstruction] = (uint8_t)classInfo.fields.count; + } + + // Clear symbol tables for tracking field and method names. + wrenSymbolTableClear(compiler->parser->vm, &classInfo.fields); + wrenIntBufferClear(compiler->parser->vm, &classInfo.methods); + wrenIntBufferClear(compiler->parser->vm, &classInfo.staticMethods); + compiler->enclosingClass = NULL; + popScope(compiler); +} + +// Compiles an "import" statement. +// +// An import compiles to a series of instructions. Given: +// +// import "foo" for Bar, Baz +// +// We compile a single IMPORT_MODULE "foo" instruction to load the module +// itself. When that finishes executing the imported module, it leaves the +// ObjModule in vm->lastModule. Then, for Bar and Baz, we: +// +// * Declare a variable in the current scope with that name. +// * Emit an IMPORT_VARIABLE instruction to load the variable's value from the +// other module. +// * Compile the code to store that value in the variable in this scope. +static void import(Compiler* compiler) { + ignoreNewlines(compiler); + consume(compiler, TOKEN_STRING, "Expect a string after 'import'."); + int moduleConstant = addConstant(compiler, compiler->parser->previous.value); + + // Load the module. + emitShortArg(compiler, CODE_IMPORT_MODULE, moduleConstant); + + // Discard the unused result value from calling the module body's closure. + emitOp(compiler, CODE_POP); + + // The for clause is optional. + if (!match(compiler, TOKEN_FOR)) return; + + // Compile the comma-separated list of variables to import. + do { + ignoreNewlines(compiler); + + consume(compiler, TOKEN_NAME, "Expect variable name."); + + // We need to hold onto the source variable, + // in order to reference it in the import later + Token sourceVariableToken = compiler->parser->previous; + + // Define a string constant for the original variable name. + int sourceVariableConstant = addConstant( + compiler, + wrenNewStringLength(compiler->parser->vm, sourceVariableToken.start, sourceVariableToken.length)); + + // Store the symbol we care about for the variable + int slot = -1; + if (match(compiler, TOKEN_AS)) { + //import "module" for Source as Dest + //Use 'Dest' as the name by declaring a new variable for it. + //This parses a name after the 'as' and defines it. + slot = declareNamedVariable(compiler); + } else { + //import "module" for Source + //Uses 'Source' as the name directly + slot = declareVariable(compiler, &sourceVariableToken); + } + + // Load the variable from the other module. + emitShortArg(compiler, CODE_IMPORT_VARIABLE, sourceVariableConstant); + + // Store the result in the variable here. + defineVariable(compiler, slot); + } while (match(compiler, TOKEN_COMMA)); +} + +// Compiles a "var" variable definition statement. +static void variableDefinition(Compiler* compiler) { + // Grab its name, but don't declare it yet. A (local) variable shouldn't be + // in scope in its own initializer. + consume(compiler, TOKEN_NAME, "Expect variable name."); + Token nameToken = compiler->parser->previous; + + // Compile the initializer. + if (match(compiler, TOKEN_EQ)) { + ignoreNewlines(compiler); + expression(compiler); + } else { + // Default initialize it to null. + null(compiler, false); + } + + // Now put it in scope. + int symbol = declareVariable(compiler, &nameToken); + defineVariable(compiler, symbol); +} + +// Compiles a "definition". These are the statements that bind new variables. +// They can only appear at the top level of a block and are prohibited in places +// like the non-curly body of an if or while. +void definition(Compiler* compiler) { + if (matchAttribute(compiler)) { + definition(compiler); + return; + } + + if (match(compiler, TOKEN_CLASS)) { + classDefinition(compiler, false); + return; + } else if (match(compiler, TOKEN_FOREIGN)) { + consume(compiler, TOKEN_CLASS, "Expect 'class' after 'foreign'."); + classDefinition(compiler, true); + return; + } + + disallowAttributes(compiler); + + if (match(compiler, TOKEN_IMPORT)) { + import(compiler); + } else if (match(compiler, TOKEN_VAR)) { + variableDefinition(compiler); + } else { + statement(compiler); + } +} + +ObjFn* wrenCompile(WrenVM* vm, ObjModule* module, const char* source, bool isExpression, bool printErrors) { + // Skip the UTF-8 BOM if there is one. + if (strncmp(source, "\xEF\xBB\xBF", 3) == 0) source += 3; + + Parser parser; + parser.vm = vm; + parser.module = module; + parser.source = source; + + parser.tokenStart = source; + parser.currentChar = source; + parser.currentLine = 1; + parser.numParens = 0; + + // Zero-init the current token. This will get copied to previous when + // nextToken() is called below. + parser.next.type = TOKEN_ERROR; + parser.next.start = source; + parser.next.length = 0; + parser.next.line = 0; + parser.next.value = UNDEFINED_VAL; + + parser.printErrors = printErrors; + parser.hasError = false; + + // Read the first token into next + nextToken(&parser); + // Copy next -> current + nextToken(&parser); + + int numExistingVariables = module->variables.count; + + Compiler compiler; + initCompiler(&compiler, &parser, NULL, false); + ignoreNewlines(&compiler); + + if (isExpression) { + expression(&compiler); + consume(&compiler, TOKEN_EOF, "Expect end of expression."); + } else { + while (!match(&compiler, TOKEN_EOF)) { + definition(&compiler); + + // If there is no newline, it must be the end of file on the same line. + if (!matchLine(&compiler)) { + consume(&compiler, TOKEN_EOF, "Expect end of file."); + break; + } + } + + emitOp(&compiler, CODE_END_MODULE); + } + + emitOp(&compiler, CODE_RETURN); + + // See if there are any implicitly declared module-level variables that never + // got an explicit definition. They will have values that are numbers + // indicating the line where the variable was first used. + for (int i = numExistingVariables; i < parser.module->variables.count; i++) { + if (IS_NUM(parser.module->variables.data[i])) { + // Synthesize a token for the original use site. + parser.previous.type = TOKEN_NAME; + parser.previous.start = parser.module->variableNames.data[i]->value; + parser.previous.length = parser.module->variableNames.data[i]->length; + parser.previous.line = (int)AS_NUM(parser.module->variables.data[i]); + error(&compiler, "Variable is used but not defined."); + } + } + + return endCompiler(&compiler, "(script)", 8); +} + +void wrenBindMethodCode(ObjClass* classObj, ObjFn* fn) { + int ip = 0; + for (;;) { + Code instruction = (Code)fn->code.data[ip]; + switch (instruction) { + case CODE_LOAD_FIELD: + case CODE_STORE_FIELD: + case CODE_LOAD_FIELD_THIS: + case CODE_STORE_FIELD_THIS: + // Shift this class's fields down past the inherited ones. We don't + // check for overflow here because we'll see if the number of fields + // overflows when the subclass is created. + fn->code.data[ip + 1] += classObj->superclass->numFields; + break; + + case CODE_SUPER_0: + case CODE_SUPER_1: + case CODE_SUPER_2: + case CODE_SUPER_3: + case CODE_SUPER_4: + case CODE_SUPER_5: + case CODE_SUPER_6: + case CODE_SUPER_7: + case CODE_SUPER_8: + case CODE_SUPER_9: + case CODE_SUPER_10: + case CODE_SUPER_11: + case CODE_SUPER_12: + case CODE_SUPER_13: + case CODE_SUPER_14: + case CODE_SUPER_15: + case CODE_SUPER_16: { + // Fill in the constant slot with a reference to the superclass. + int constant = (fn->code.data[ip + 3] << 8) | fn->code.data[ip + 4]; + fn->constants.data[constant] = OBJ_VAL(classObj->superclass); + break; + } + + case CODE_CLOSURE: { + // Bind the nested closure too. + int constant = (fn->code.data[ip + 1] << 8) | fn->code.data[ip + 2]; + wrenBindMethodCode(classObj, AS_FN(fn->constants.data[constant])); + break; + } + + case CODE_END: + return; + + default: + // Other instructions are unaffected, so just skip over them. + break; + } + ip += 1 + getByteCountForArguments(fn->code.data, fn->constants.data, ip); + } +} + +void wrenMarkCompiler(WrenVM* vm, Compiler* compiler) { + wrenGrayValue(vm, compiler->parser->current.value); + wrenGrayValue(vm, compiler->parser->previous.value); + wrenGrayValue(vm, compiler->parser->next.value); + + // Walk up the parent chain to mark the outer compilers too. The VM only + // tracks the innermost one. + do { + wrenGrayObj(vm, (Obj*)compiler->fn); + wrenGrayObj(vm, (Obj*)compiler->constants); + wrenGrayObj(vm, (Obj*)compiler->attributes); + + if (compiler->enclosingClass != NULL) { + wrenBlackenSymbolTable(vm, &compiler->enclosingClass->fields); + + if (compiler->enclosingClass->methodAttributes != NULL) { + wrenGrayObj(vm, (Obj*)compiler->enclosingClass->methodAttributes); + } + if (compiler->enclosingClass->classAttributes != NULL) { + wrenGrayObj(vm, (Obj*)compiler->enclosingClass->classAttributes); + } + } + + compiler = compiler->parent; + } while (compiler != NULL); +} + +// Helpers for Attributes + +// Throw an error if any attributes were found preceding, +// and clear the attributes so the error doesn't keep happening. +static void disallowAttributes(Compiler* compiler) { + if (compiler->numAttributes > 0) { + error(compiler, "Attributes can only specified before a class or a method"); + wrenMapClear(compiler->parser->vm, compiler->attributes); + compiler->numAttributes = 0; + } +} + +// Add an attribute to a given group in the compiler attribues map +static void addToAttributeGroup(Compiler* compiler, Value group, Value key, Value value) { + WrenVM* vm = compiler->parser->vm; + + if (IS_OBJ(group)) wrenPushRoot(vm, AS_OBJ(group)); + if (IS_OBJ(key)) wrenPushRoot(vm, AS_OBJ(key)); + if (IS_OBJ(value)) wrenPushRoot(vm, AS_OBJ(value)); + + Value groupMapValue = wrenMapGet(compiler->attributes, group); + if (IS_UNDEFINED(groupMapValue)) { + groupMapValue = OBJ_VAL(wrenNewMap(vm)); + wrenMapSet(vm, compiler->attributes, group, groupMapValue); + } + + //we store them as a map per so we can maintain duplicate keys + //group = { key:[value, ...], } + ObjMap* groupMap = AS_MAP(groupMapValue); + + //var keyItems = group[key] + //if(!keyItems) keyItems = group[key] = [] + Value keyItemsValue = wrenMapGet(groupMap, key); + if (IS_UNDEFINED(keyItemsValue)) { + keyItemsValue = OBJ_VAL(wrenNewList(vm, 0)); + wrenMapSet(vm, groupMap, key, keyItemsValue); + } + + //keyItems.add(value) + ObjList* keyItems = AS_LIST(keyItemsValue); + wrenValueBufferWrite(vm, &keyItems->elements, value); + + if (IS_OBJ(group)) wrenPopRoot(vm); + if (IS_OBJ(key)) wrenPopRoot(vm); + if (IS_OBJ(value)) wrenPopRoot(vm); +} + +// Emit the attributes in the give map onto the stack +static void emitAttributes(Compiler* compiler, ObjMap* attributes) { + // Instantiate a new map for the attributes + loadCoreVariable(compiler, "Map"); + callMethod(compiler, 0, "new()", 5); + + // The attributes are stored as group = { key:[value, value, ...] } + // so our first level is the group map + for (uint32_t groupIdx = 0; groupIdx < attributes->capacity; groupIdx++) { + const MapEntry* groupEntry = &attributes->entries[groupIdx]; + if (IS_UNDEFINED(groupEntry->key)) continue; + //group key + emitConstant(compiler, groupEntry->key); + + //group value is gonna be a map + loadCoreVariable(compiler, "Map"); + callMethod(compiler, 0, "new()", 5); + + ObjMap* groupItems = AS_MAP(groupEntry->value); + for (uint32_t itemIdx = 0; itemIdx < groupItems->capacity; itemIdx++) { + const MapEntry* itemEntry = &groupItems->entries[itemIdx]; + if (IS_UNDEFINED(itemEntry->key)) continue; + + emitConstant(compiler, itemEntry->key); + // Attribute key value, key = [] + loadCoreVariable(compiler, "List"); + callMethod(compiler, 0, "new()", 5); + // Add the items to the key list + ObjList* items = AS_LIST(itemEntry->value); + for (int itemIdx = 0; itemIdx < items->elements.count; ++itemIdx) { + emitConstant(compiler, items->elements.data[itemIdx]); + callMethod(compiler, 1, "addCore_(_)", 11); + } + // Add the list to the map + callMethod(compiler, 2, "addCore_(_,_)", 13); + } + + // Add the key/value to the map + callMethod(compiler, 2, "addCore_(_,_)", 13); + } +} + +// Methods are stored as method <-> attributes, so we have to have +// an indirection to resolve for methods +static void emitAttributeMethods(Compiler* compiler, ObjMap* attributes) { + // Instantiate a new map for the attributes + loadCoreVariable(compiler, "Map"); + callMethod(compiler, 0, "new()", 5); + + for (uint32_t methodIdx = 0; methodIdx < attributes->capacity; methodIdx++) { + const MapEntry* methodEntry = &attributes->entries[methodIdx]; + if (IS_UNDEFINED(methodEntry->key)) continue; + emitConstant(compiler, methodEntry->key); + ObjMap* attributeMap = AS_MAP(methodEntry->value); + emitAttributes(compiler, attributeMap); + callMethod(compiler, 2, "addCore_(_,_)", 13); + } +} + +// Emit the final ClassAttributes that exists at runtime +static void emitClassAttributes(Compiler* compiler, ClassInfo* classInfo) { + loadCoreVariable(compiler, "ClassAttributes"); + + classInfo->classAttributes ? emitAttributes(compiler, classInfo->classAttributes) : null(compiler, false); + + classInfo->methodAttributes ? emitAttributeMethods(compiler, classInfo->methodAttributes) : null(compiler, false); + + callMethod(compiler, 2, "new(_,_)", 8); +} + +// Copy the current attributes stored in the compiler into a destination map +// This also resets the counter, since the intent is to consume the attributes +static void copyAttributes(Compiler* compiler, ObjMap* into) { + compiler->numAttributes = 0; + + if (compiler->attributes->count == 0) return; + if (into == NULL) return; + + WrenVM* vm = compiler->parser->vm; + + // Note we copy the actual values as is since we'll take ownership + // and clear the original map + for (uint32_t attrIdx = 0; attrIdx < compiler->attributes->capacity; attrIdx++) { + const MapEntry* attrEntry = &compiler->attributes->entries[attrIdx]; + if (IS_UNDEFINED(attrEntry->key)) continue; + wrenMapSet(vm, into, attrEntry->key, attrEntry->value); + } + + wrenMapClear(vm, compiler->attributes); +} + +// Copy the current attributes stored in the compiler into the method specific +// attributes for the current enclosingClass. +// This also resets the counter, since the intent is to consume the attributes +static void copyMethodAttributes(Compiler* compiler, bool isForeign, bool isStatic, const char* fullSignature, + int32_t length) { + compiler->numAttributes = 0; + + if (compiler->attributes->count == 0) return; + + WrenVM* vm = compiler->parser->vm; + + // Make a map for this method to copy into + ObjMap* methodAttr = wrenNewMap(vm); + wrenPushRoot(vm, (Obj*)methodAttr); + copyAttributes(compiler, methodAttr); + + // Include 'foreign static ' in front as needed + int32_t fullLength = length; + if (isForeign) fullLength += 8; + if (isStatic) fullLength += 7; + char fullSignatureWithPrefix[MAX_METHOD_SIGNATURE + 8 + 7]; + const char* foreignPrefix = isForeign ? "foreign " : ""; + const char* staticPrefix = isStatic ? "static " : ""; + sprintf(fullSignatureWithPrefix, "%s%s%.*s", foreignPrefix, staticPrefix, length, fullSignature); + fullSignatureWithPrefix[fullLength] = '\0'; + + if (compiler->enclosingClass->methodAttributes == NULL) { + compiler->enclosingClass->methodAttributes = wrenNewMap(vm); + } + + // Store the method attributes in the class map + Value key = wrenNewStringLength(vm, fullSignatureWithPrefix, fullLength); + wrenMapSet(vm, compiler->enclosingClass->methodAttributes, key, OBJ_VAL(methodAttr)); + + wrenPopRoot(vm); +} +// End file "wren_compiler.c" +// Begin file "wren_debug.c" +#include + +void wrenDebugPrintStackTrace(WrenVM* vm) { + // Bail if the host doesn't enable printing errors. + if (vm->config.errorFn == NULL) return; + + ObjFiber* fiber = vm->fiber; + if (IS_STRING(fiber->error)) { + vm->config.errorFn(vm, WREN_ERROR_RUNTIME, NULL, -1, AS_CSTRING(fiber->error)); + } else { + // TODO: Print something a little useful here. Maybe the name of the error's + // class? + vm->config.errorFn(vm, WREN_ERROR_RUNTIME, NULL, -1, "[error object]"); + } + + for (int i = fiber->numFrames - 1; i >= 0; i--) { + CallFrame* frame = &fiber->frames[i]; + ObjFn* fn = frame->closure->fn; + + // Skip over stub functions for calling methods from the C API. + if (fn->module == NULL) continue; + + // The built-in core module has no name. We explicitly omit it from stack + // traces since we don't want to highlight to a user the implementation + // detail of what part of the core module is written in C and what is Wren. + if (fn->module->name == NULL) continue; + + // -1 because IP has advanced past the instruction that it just executed. + int line = fn->debug->sourceLines.data[frame->ip - fn->code.data - 1]; + vm->config.errorFn(vm, WREN_ERROR_STACK_TRACE, fn->module->name->value, line, fn->debug->name); + } +} + +static void dumpObject(Obj* obj) { + switch (obj->type) { + case OBJ_CLASS: + printf("[class %s %p]", ((ObjClass*)obj)->name->value, obj); + break; + case OBJ_CLOSURE: + printf("[closure %p]", obj); + break; + case OBJ_FIBER: + printf("[fiber %p]", obj); + break; + case OBJ_FN: + printf("[fn %p]", obj); + break; + case OBJ_FOREIGN: + printf("[foreign %p]", obj); + break; + case OBJ_INSTANCE: + printf("[instance %p]", obj); + break; + case OBJ_LIST: + printf("[list %p]", obj); + break; + case OBJ_MAP: + printf("[map %p]", obj); + break; + case OBJ_MODULE: + printf("[module %p]", obj); + break; + case OBJ_RANGE: + printf("[range %p]", obj); + break; + case OBJ_STRING: + printf("%s", ((ObjString*)obj)->value); + break; + case OBJ_UPVALUE: + printf("[upvalue %p]", obj); + break; + default: + printf("[unknown object %d]", obj->type); + break; + } +} + +void wrenDumpValue(Value value) { +#if WREN_NAN_TAGGING + if (IS_NUM(value)) { + printf("%.14g", AS_NUM(value)); + } else if (IS_OBJ(value)) { + dumpObject(AS_OBJ(value)); + } else { + switch (GET_TAG(value)) { + case TAG_FALSE: + printf("false"); + break; + case TAG_NAN: + printf("NaN"); + break; + case TAG_NULL: + printf("null"); + break; + case TAG_TRUE: + printf("true"); + break; + case TAG_UNDEFINED: + UNREACHABLE(); + } + } +#else + switch (value.type) { + case VAL_FALSE: + printf("false"); + break; + case VAL_NULL: + printf("null"); + break; + case VAL_NUM: + printf("%.14g", AS_NUM(value)); + break; + case VAL_TRUE: + printf("true"); + break; + case VAL_OBJ: + dumpObject(AS_OBJ(value)); + break; + case VAL_UNDEFINED: + UNREACHABLE(); + } +#endif +} + +static int dumpInstruction(WrenVM* vm, ObjFn* fn, int i, int* lastLine) { + int start = i; + uint8_t* bytecode = fn->code.data; + Code code = (Code)bytecode[i]; + + int line = fn->debug->sourceLines.data[i]; + if (lastLine == NULL || *lastLine != line) { + printf("%4d:", line); + if (lastLine != NULL) *lastLine = line; + } else { + printf(" "); + } + + printf(" %04d ", i++); + +#define READ_BYTE() (bytecode[i++]) +#define READ_SHORT() (i += 2, (bytecode[i - 2] << 8) | bytecode[i - 1]) + +#define BYTE_INSTRUCTION(name) \ + printf("%-16s %5d\n", name, READ_BYTE()); \ + break + + switch (code) { + case CODE_CONSTANT: { + int constant = READ_SHORT(); + printf("%-16s %5d '", "CONSTANT", constant); + wrenDumpValue(fn->constants.data[constant]); + printf("'\n"); + break; + } + + case CODE_NULL: + printf("NULL\n"); + break; + case CODE_FALSE: + printf("FALSE\n"); + break; + case CODE_TRUE: + printf("TRUE\n"); + break; + + case CODE_LOAD_LOCAL_0: + printf("LOAD_LOCAL_0\n"); + break; + case CODE_LOAD_LOCAL_1: + printf("LOAD_LOCAL_1\n"); + break; + case CODE_LOAD_LOCAL_2: + printf("LOAD_LOCAL_2\n"); + break; + case CODE_LOAD_LOCAL_3: + printf("LOAD_LOCAL_3\n"); + break; + case CODE_LOAD_LOCAL_4: + printf("LOAD_LOCAL_4\n"); + break; + case CODE_LOAD_LOCAL_5: + printf("LOAD_LOCAL_5\n"); + break; + case CODE_LOAD_LOCAL_6: + printf("LOAD_LOCAL_6\n"); + break; + case CODE_LOAD_LOCAL_7: + printf("LOAD_LOCAL_7\n"); + break; + case CODE_LOAD_LOCAL_8: + printf("LOAD_LOCAL_8\n"); + break; + + case CODE_LOAD_LOCAL: + BYTE_INSTRUCTION("LOAD_LOCAL"); + case CODE_STORE_LOCAL: + BYTE_INSTRUCTION("STORE_LOCAL"); + case CODE_LOAD_UPVALUE: + BYTE_INSTRUCTION("LOAD_UPVALUE"); + case CODE_STORE_UPVALUE: + BYTE_INSTRUCTION("STORE_UPVALUE"); + + case CODE_LOAD_MODULE_VAR: { + int slot = READ_SHORT(); + printf("%-16s %5d '%s'\n", "LOAD_MODULE_VAR", slot, fn->module->variableNames.data[slot]->value); + break; + } + + case CODE_STORE_MODULE_VAR: { + int slot = READ_SHORT(); + printf("%-16s %5d '%s'\n", "STORE_MODULE_VAR", slot, fn->module->variableNames.data[slot]->value); + break; + } + + case CODE_LOAD_FIELD_THIS: + BYTE_INSTRUCTION("LOAD_FIELD_THIS"); + case CODE_STORE_FIELD_THIS: + BYTE_INSTRUCTION("STORE_FIELD_THIS"); + case CODE_LOAD_FIELD: + BYTE_INSTRUCTION("LOAD_FIELD"); + case CODE_STORE_FIELD: + BYTE_INSTRUCTION("STORE_FIELD"); + + case CODE_POP: + printf("POP\n"); + break; + + case CODE_CALL_0: + case CODE_CALL_1: + case CODE_CALL_2: + case CODE_CALL_3: + case CODE_CALL_4: + case CODE_CALL_5: + case CODE_CALL_6: + case CODE_CALL_7: + case CODE_CALL_8: + case CODE_CALL_9: + case CODE_CALL_10: + case CODE_CALL_11: + case CODE_CALL_12: + case CODE_CALL_13: + case CODE_CALL_14: + case CODE_CALL_15: + case CODE_CALL_16: { + int numArgs = bytecode[i - 1] - CODE_CALL_0; + int symbol = READ_SHORT(); + printf("CALL_%-11d %5d '%s'\n", numArgs, symbol, vm->methodNames.data[symbol]->value); + break; + } + + case CODE_SUPER_0: + case CODE_SUPER_1: + case CODE_SUPER_2: + case CODE_SUPER_3: + case CODE_SUPER_4: + case CODE_SUPER_5: + case CODE_SUPER_6: + case CODE_SUPER_7: + case CODE_SUPER_8: + case CODE_SUPER_9: + case CODE_SUPER_10: + case CODE_SUPER_11: + case CODE_SUPER_12: + case CODE_SUPER_13: + case CODE_SUPER_14: + case CODE_SUPER_15: + case CODE_SUPER_16: { + int numArgs = bytecode[i - 1] - CODE_SUPER_0; + int symbol = READ_SHORT(); + int superclass = READ_SHORT(); + printf("SUPER_%-10d %5d '%s' %5d\n", numArgs, symbol, vm->methodNames.data[symbol]->value, superclass); + break; + } + + case CODE_JUMP: { + int offset = READ_SHORT(); + printf("%-16s %5d to %d\n", "JUMP", offset, i + offset); + break; + } + + case CODE_LOOP: { + int offset = READ_SHORT(); + printf("%-16s %5d to %d\n", "LOOP", offset, i - offset); + break; + } + + case CODE_JUMP_IF: { + int offset = READ_SHORT(); + printf("%-16s %5d to %d\n", "JUMP_IF", offset, i + offset); + break; + } + + case CODE_AND: { + int offset = READ_SHORT(); + printf("%-16s %5d to %d\n", "AND", offset, i + offset); + break; + } + + case CODE_OR: { + int offset = READ_SHORT(); + printf("%-16s %5d to %d\n", "OR", offset, i + offset); + break; + } + + case CODE_CLOSE_UPVALUE: + printf("CLOSE_UPVALUE\n"); + break; + case CODE_RETURN: + printf("RETURN\n"); + break; + + case CODE_CLOSURE: { + int constant = READ_SHORT(); + printf("%-16s %5d ", "CLOSURE", constant); + wrenDumpValue(fn->constants.data[constant]); + printf(" "); + ObjFn* loadedFn = AS_FN(fn->constants.data[constant]); + for (int j = 0; j < loadedFn->numUpvalues; j++) { + int isLocal = READ_BYTE(); + int index = READ_BYTE(); + if (j > 0) printf(", "); + printf("%s %d", isLocal ? "local" : "upvalue", index); + } + printf("\n"); + break; + } + + case CODE_CONSTRUCT: + printf("CONSTRUCT\n"); + break; + case CODE_FOREIGN_CONSTRUCT: + printf("FOREIGN_CONSTRUCT\n"); + break; + + case CODE_CLASS: { + int numFields = READ_BYTE(); + printf("%-16s %5d fields\n", "CLASS", numFields); + break; + } + + case CODE_FOREIGN_CLASS: + printf("FOREIGN_CLASS\n"); + break; + case CODE_END_CLASS: + printf("END_CLASS\n"); + break; + + case CODE_METHOD_INSTANCE: { + int symbol = READ_SHORT(); + printf("%-16s %5d '%s'\n", "METHOD_INSTANCE", symbol, vm->methodNames.data[symbol]->value); + break; + } + + case CODE_METHOD_STATIC: { + int symbol = READ_SHORT(); + printf("%-16s %5d '%s'\n", "METHOD_STATIC", symbol, vm->methodNames.data[symbol]->value); + break; + } + + case CODE_END_MODULE: + printf("END_MODULE\n"); + break; + + case CODE_IMPORT_MODULE: { + int name = READ_SHORT(); + printf("%-16s %5d '", "IMPORT_MODULE", name); + wrenDumpValue(fn->constants.data[name]); + printf("'\n"); + break; + } + + case CODE_IMPORT_VARIABLE: { + int variable = READ_SHORT(); + printf("%-16s %5d '", "IMPORT_VARIABLE", variable); + wrenDumpValue(fn->constants.data[variable]); + printf("'\n"); + break; + } + + case CODE_END: + printf("END\n"); + break; + + default: + printf("UKNOWN! [%d]\n", bytecode[i - 1]); + break; + } + + // Return how many bytes this instruction takes, or -1 if it's an END. + if (code == CODE_END) return -1; + return i - start; + +#undef READ_BYTE +#undef READ_SHORT +} + +int wrenDumpInstruction(WrenVM* vm, ObjFn* fn, int i) { + return dumpInstruction(vm, fn, i, NULL); +} + +void wrenDumpCode(WrenVM* vm, ObjFn* fn) { + printf("%s: %s\n", fn->module->name == NULL ? "" : fn->module->name->value, fn->debug->name); + + int i = 0; + int lastLine = -1; + for (;;) { + int offset = dumpInstruction(vm, fn, i, &lastLine); + if (offset == -1) break; + i += offset; + } + + printf("\n"); +} + +void wrenDumpStack(ObjFiber* fiber) { + printf("(fiber %p) ", fiber); + for (Value* slot = fiber->stack; slot < fiber->stackTop; slot++) { + wrenDumpValue(*slot); + printf(" | "); + } + printf("\n"); +} +// End file "wren_debug.c" +// Begin file "wren_vm.c" +#include +#include + +// Begin file "wren_core.h" +#ifndef wren_core_h +#define wren_core_h + +// This module defines the built-in classes and their primitives methods that +// are implemented directly in C code. Some languages try to implement as much +// of the core module itself in the primary language instead of in the host +// language. +// +// With Wren, we try to do as much of it in C as possible. Primitive methods +// are always faster than code written in Wren, and it minimizes startup time +// since we don't have to parse, compile, and execute Wren code. +// +// There is one limitation, though. Methods written in C cannot call Wren ones. +// They can only be the top of the callstack, and immediately return. This +// makes it difficult to have primitive methods that rely on polymorphic +// behavior. For example, `System.print` should call `toString` on its argument, +// including user-defined `toString` methods on user-defined classes. + +void wrenInitializeCore(WrenVM* vm); + +#endif +// End file "wren_core.h" +// Begin file "wren_primitive.h" +#ifndef wren_primitive_h +#define wren_primitive_h + +// Binds a primitive method named [name] (in Wren) implemented using C function +// [fn] to `ObjClass` [cls]. +#define PRIMITIVE(cls, name, function) \ + do { \ + int symbol = wrenSymbolTableEnsure(vm, &vm->methodNames, name, strlen(name)); \ + Method method; \ + method.type = METHOD_PRIMITIVE; \ + method.as.primitive = prim_##function; \ + wrenBindMethod(vm, cls, symbol, method); \ + } while (false) + +// Binds a primitive method named [name] (in Wren) implemented using C function +// [fn] to `ObjClass` [cls], but as a FN call. +#define FUNCTION_CALL(cls, name, function) \ + do { \ + int symbol = wrenSymbolTableEnsure(vm, &vm->methodNames, name, strlen(name)); \ + Method method; \ + method.type = METHOD_FUNCTION_CALL; \ + method.as.primitive = prim_##function; \ + wrenBindMethod(vm, cls, symbol, method); \ + } while (false) + +// Defines a primitive method whose C function name is [name]. This abstracts +// the actual type signature of a primitive function and makes it clear which C +// functions are invoked as primitives. +#define DEF_PRIMITIVE(name) static bool prim_##name(WrenVM* vm, Value* args) + +#define RETURN_VAL(value) \ + do { \ + args[0] = value; \ + return true; \ + } while (false) + +#define RETURN_OBJ(obj) RETURN_VAL(OBJ_VAL(obj)) +#define RETURN_BOOL(value) RETURN_VAL(BOOL_VAL(value)) +#define RETURN_FALSE RETURN_VAL(FALSE_VAL) +#define RETURN_NULL RETURN_VAL(NULL_VAL) +#define RETURN_NUM(value) RETURN_VAL(NUM_VAL(value)) +#define RETURN_TRUE RETURN_VAL(TRUE_VAL) + +#define RETURN_ERROR(msg) \ + do { \ + vm->fiber->error = wrenNewStringLength(vm, msg, sizeof(msg) - 1); \ + return false; \ + } while (false) + +#define RETURN_ERROR_FMT(...) \ + do { \ + vm->fiber->error = wrenStringFormat(vm, __VA_ARGS__); \ + return false; \ + } while (false) + +// Validates that the given [arg] is a function. Returns true if it is. If not, +// reports an error and returns false. +bool validateFn(WrenVM* vm, Value arg, const char* argName); + +// Validates that the given [arg] is a Num. Returns true if it is. If not, +// reports an error and returns false. +bool validateNum(WrenVM* vm, Value arg, const char* argName); + +// Validates that [value] is an integer. Returns true if it is. If not, reports +// an error and returns false. +bool validateIntValue(WrenVM* vm, double value, const char* argName); + +// Validates that the given [arg] is an integer. Returns true if it is. If not, +// reports an error and returns false. +bool validateInt(WrenVM* vm, Value arg, const char* argName); + +// Validates that [arg] is a valid object for use as a map key. Returns true if +// it is. If not, reports an error and returns false. +bool validateKey(WrenVM* vm, Value arg); + +// Validates that the argument at [argIndex] is an integer within `[0, count)`. +// Also allows negative indices which map backwards from the end. Returns the +// valid positive index value. If invalid, reports an error and returns +// `UINT32_MAX`. +uint32_t validateIndex(WrenVM* vm, Value arg, uint32_t count, const char* argName); + +// Validates that the given [arg] is a String. Returns true if it is. If not, +// reports an error and returns false. +bool validateString(WrenVM* vm, Value arg, const char* argName); + +// Given a [range] and the [length] of the object being operated on, determines +// the series of elements that should be chosen from the underlying object. +// Handles ranges that count backwards from the end as well as negative ranges. +// +// Returns the index from which the range should start or `UINT32_MAX` if the +// range is invalid. After calling, [length] will be updated with the number of +// elements in the resulting sequence. [step] will be direction that the range +// is going: `1` if the range is increasing from the start index or `-1` if the +// range is decreasing. +uint32_t calculateRange(WrenVM* vm, ObjRange* range, uint32_t* length, int* step); + +#endif +// End file "wren_primitive.h" + +#if WREN_OPT_META +// Begin file "wren_opt_meta.h" +#ifndef wren_opt_meta_h +#define wren_opt_meta_h + +// This module defines the Meta class and its associated methods. +#if WREN_OPT_META + +const char* wrenMetaSource(); +WrenForeignMethodFn wrenMetaBindForeignMethod(WrenVM* vm, const char* className, bool isStatic, const char* signature); + +#endif + +#endif +// End file "wren_opt_meta.h" +#endif +#if WREN_OPT_RANDOM +// Begin file "wren_opt_random.h" +#ifndef wren_opt_random_h +#define wren_opt_random_h + +#if WREN_OPT_RANDOM + +const char* wrenRandomSource(); +WrenForeignClassMethods wrenRandomBindForeignClass(WrenVM* vm, const char* module, const char* className); +WrenForeignMethodFn wrenRandomBindForeignMethod(WrenVM* vm, const char* className, bool isStatic, + const char* signature); + +#endif + +#endif +// End file "wren_opt_random.h" +#endif + +#if WREN_DEBUG_TRACE_MEMORY || WREN_DEBUG_TRACE_GC +#include +#include +#endif + +// The behavior of realloc() when the size is 0 is implementation defined. It +// may return a non-NULL pointer which must not be dereferenced but nevertheless +// should be freed. To prevent that, we avoid calling realloc() with a zero +// size. +static void* defaultReallocate(void* ptr, size_t newSize, void* _) { + if (newSize == 0) { + free(ptr); + return NULL; + } + + return realloc(ptr, newSize); +} + +int wrenGetVersionNumber() { + return WREN_VERSION_NUMBER; +} + +void wrenInitConfiguration(WrenConfiguration* config) { + config->reallocateFn = defaultReallocate; + config->resolveModuleFn = NULL; + config->loadModuleFn = NULL; + config->bindForeignMethodFn = NULL; + config->bindForeignClassFn = NULL; + config->writeFn = NULL; + config->errorFn = NULL; + config->initialHeapSize = 1024 * 1024 * 10; + config->minHeapSize = 1024 * 1024; + config->heapGrowthPercent = 50; + config->userData = NULL; +} + +WrenVM* wrenNewVM(WrenConfiguration* config) { + WrenReallocateFn reallocate = defaultReallocate; + void* userData = NULL; + if (config != NULL) { + userData = config->userData; + reallocate = config->reallocateFn ? config->reallocateFn : defaultReallocate; + } + + WrenVM* vm = (WrenVM*)reallocate(NULL, sizeof(*vm), userData); + memset(vm, 0, sizeof(WrenVM)); + + // Copy the configuration if given one. + if (config != NULL) { + memcpy(&vm->config, config, sizeof(WrenConfiguration)); + + // We choose to set this after copying, + // rather than modifying the user config pointer + vm->config.reallocateFn = reallocate; + } else { + wrenInitConfiguration(&vm->config); + } + + // TODO: Should we allocate and free this during a GC? + vm->grayCount = 0; + // TODO: Tune this. + vm->grayCapacity = 4; + vm->gray = (Obj**)reallocate(NULL, vm->grayCapacity * sizeof(Obj*), userData); + vm->nextGC = vm->config.initialHeapSize; + + wrenSymbolTableInit(&vm->methodNames); + + vm->modules = wrenNewMap(vm); + wrenInitializeCore(vm); + return vm; +} + +void wrenFreeVM(WrenVM* vm) { + ASSERT(vm->methodNames.count > 0, "VM appears to have already been freed."); + + // Free all of the GC objects. + Obj* obj = vm->first; + while (obj != NULL) { + Obj* next = obj->next; + wrenFreeObj(vm, obj); + obj = next; + } + + // Free up the GC gray set. + vm->gray = (Obj**)vm->config.reallocateFn(vm->gray, 0, vm->config.userData); + + // Tell the user if they didn't free any handles. We don't want to just free + // them here because the host app may still have pointers to them that they + // may try to use. Better to tell them about the bug early. + ASSERT(vm->handles == NULL, "All handles have not been released."); + + wrenSymbolTableClear(vm, &vm->methodNames); + + DEALLOCATE(vm, vm); +} + +void wrenCollectGarbage(WrenVM* vm) { +#if WREN_DEBUG_TRACE_MEMORY || WREN_DEBUG_TRACE_GC + printf("-- gc --\n"); + + size_t before = vm->bytesAllocated; + double startTime = (double)clock() / CLOCKS_PER_SEC; +#endif + + // Mark all reachable objects. + + // Reset this. As we mark objects, their size will be counted again so that + // we can track how much memory is in use without needing to know the size + // of each *freed* object. + // + // This is important because when freeing an unmarked object, we don't always + // know how much memory it is using. For example, when freeing an instance, + // we need to know its class to know how big it is, but its class may have + // already been freed. + vm->bytesAllocated = 0; + + wrenGrayObj(vm, (Obj*)vm->modules); + + // Temporary roots. + for (int i = 0; i < vm->numTempRoots; i++) { + wrenGrayObj(vm, vm->tempRoots[i]); + } + + // The current fiber. + wrenGrayObj(vm, (Obj*)vm->fiber); + + // The handles. + for (WrenHandle* handle = vm->handles; handle != NULL; handle = handle->next) { + wrenGrayValue(vm, handle->value); + } + + // Any object the compiler is using (if there is one). + if (vm->compiler != NULL) wrenMarkCompiler(vm, vm->compiler); + + // Method names. + wrenBlackenSymbolTable(vm, &vm->methodNames); + + // Now that we have grayed the roots, do a depth-first search over all of the + // reachable objects. + wrenBlackenObjects(vm); + + // Collect the white objects. + Obj** obj = &vm->first; + while (*obj != NULL) { + if (!((*obj)->isDark)) { + // This object wasn't reached, so remove it from the list and free it. + Obj* unreached = *obj; + *obj = unreached->next; + wrenFreeObj(vm, unreached); + } else { + // This object was reached, so unmark it (for the next GC) and move on to + // the next. + (*obj)->isDark = false; + obj = &(*obj)->next; + } + } + + // Calculate the next gc point, this is the current allocation plus + // a configured percentage of the current allocation. + vm->nextGC = vm->bytesAllocated + ((vm->bytesAllocated * vm->config.heapGrowthPercent) / 100); + if (vm->nextGC < vm->config.minHeapSize) vm->nextGC = vm->config.minHeapSize; + +#if WREN_DEBUG_TRACE_MEMORY || WREN_DEBUG_TRACE_GC + double elapsed = ((double)clock() / CLOCKS_PER_SEC) - startTime; + // Explicit cast because size_t has different sizes on 32-bit and 64-bit and + // we need a consistent type for the format string. + printf("GC %lu before, %lu after (%lu collected), next at %lu. Took %.3fms.\n", (unsigned long)before, + (unsigned long)vm->bytesAllocated, (unsigned long)(before - vm->bytesAllocated), (unsigned long)vm->nextGC, + elapsed * 1000.0); +#endif +} + +void* wrenReallocate(WrenVM* vm, void* memory, size_t oldSize, size_t newSize) { +#if WREN_DEBUG_TRACE_MEMORY + // Explicit cast because size_t has different sizes on 32-bit and 64-bit and + // we need a consistent type for the format string. + printf("reallocate %p %lu -> %lu\n", memory, (unsigned long)oldSize, (unsigned long)newSize); +#endif + + // If new bytes are being allocated, add them to the total count. If objects + // are being completely deallocated, we don't track that (since we don't + // track the original size). Instead, that will be handled while marking + // during the next GC. + vm->bytesAllocated += newSize - oldSize; + +#if WREN_DEBUG_GC_STRESS + // Since collecting calls this function to free things, make sure we don't + // recurse. + if (newSize > 0) wrenCollectGarbage(vm); +#else + if (newSize > 0 && vm->bytesAllocated > vm->nextGC) wrenCollectGarbage(vm); +#endif + + return vm->config.reallocateFn(memory, newSize, vm->config.userData); +} + +// Captures the local variable [local] into an [Upvalue]. If that local is +// already in an upvalue, the existing one will be used. (This is important to +// ensure that multiple closures closing over the same variable actually see +// the same variable.) Otherwise, it will create a new open upvalue and add it +// the fiber's list of upvalues. +static ObjUpvalue* captureUpvalue(WrenVM* vm, ObjFiber* fiber, Value* local) { + // If there are no open upvalues at all, we must need a new one. + if (fiber->openUpvalues == NULL) { + fiber->openUpvalues = wrenNewUpvalue(vm, local); + return fiber->openUpvalues; + } + + ObjUpvalue* prevUpvalue = NULL; + ObjUpvalue* upvalue = fiber->openUpvalues; + + // Walk towards the bottom of the stack until we find a previously existing + // upvalue or pass where it should be. + while (upvalue != NULL && upvalue->value > local) { + prevUpvalue = upvalue; + upvalue = upvalue->next; + } + + // Found an existing upvalue for this local. + if (upvalue != NULL && upvalue->value == local) return upvalue; + + // We've walked past this local on the stack, so there must not be an + // upvalue for it already. Make a new one and link it in in the right + // place to keep the list sorted. + ObjUpvalue* createdUpvalue = wrenNewUpvalue(vm, local); + if (prevUpvalue == NULL) { + // The new one is the first one in the list. + fiber->openUpvalues = createdUpvalue; + } else { + prevUpvalue->next = createdUpvalue; + } + + createdUpvalue->next = upvalue; + return createdUpvalue; +} + +// Closes any open upvalues that have been created for stack slots at [last] +// and above. +static void closeUpvalues(ObjFiber* fiber, Value* last) { + while (fiber->openUpvalues != NULL && fiber->openUpvalues->value >= last) { + ObjUpvalue* upvalue = fiber->openUpvalues; + + // Move the value into the upvalue itself and point the upvalue to it. + upvalue->closed = *upvalue->value; + upvalue->value = &upvalue->closed; + + // Remove it from the open upvalue list. + fiber->openUpvalues = upvalue->next; + } +} + +// Looks up a foreign method in [moduleName] on [className] with [signature]. +// +// This will try the host's foreign method binder first. If that fails, it +// falls back to handling the built-in modules. +static WrenForeignMethodFn findForeignMethod(WrenVM* vm, const char* moduleName, const char* className, bool isStatic, + const char* signature) { + WrenForeignMethodFn method = NULL; + + if (vm->config.bindForeignMethodFn != NULL) { + method = vm->config.bindForeignMethodFn(vm, moduleName, className, isStatic, signature); + } + + // If the host didn't provide it, see if it's an optional one. + if (method == NULL) { +#if WREN_OPT_META + if (strcmp(moduleName, "meta") == 0) { + method = wrenMetaBindForeignMethod(vm, className, isStatic, signature); + } +#endif +#if WREN_OPT_RANDOM + if (strcmp(moduleName, "random") == 0) { + method = wrenRandomBindForeignMethod(vm, className, isStatic, signature); + } +#endif + } + + return method; +} + +// Defines [methodValue] as a method on [classObj]. +// +// Handles both foreign methods where [methodValue] is a string containing the +// method's signature and Wren methods where [methodValue] is a function. +// +// Aborts the current fiber if the method is a foreign method that could not be +// found. +static void bindMethod(WrenVM* vm, int methodType, int symbol, ObjModule* module, ObjClass* classObj, + Value methodValue) { + const char* className = classObj->name->value; + if (methodType == CODE_METHOD_STATIC) classObj = classObj->obj.classObj; + + Method method; + if (IS_STRING(methodValue)) { + const char* name = AS_CSTRING(methodValue); + method.type = METHOD_FOREIGN; + method.as.foreign = + findForeignMethod(vm, module->name->value, className, methodType == CODE_METHOD_STATIC, name); + + if (method.as.foreign == NULL) { + vm->fiber->error = wrenStringFormat(vm, "Could not find foreign method '@' for class $ in module '$'.", + methodValue, classObj->name->value, module->name->value); + return; + } + } else { + method.as.closure = AS_CLOSURE(methodValue); + method.type = METHOD_BLOCK; + + // Patch up the bytecode now that we know the superclass. + wrenBindMethodCode(classObj, method.as.closure->fn); + } + + wrenBindMethod(vm, classObj, symbol, method); +} + +static void callForeign(WrenVM* vm, ObjFiber* fiber, WrenForeignMethodFn foreign, int numArgs) { + ASSERT(vm->apiStack == NULL, "Cannot already be in foreign call."); + vm->apiStack = fiber->stackTop - numArgs; + + foreign(vm); + + // Discard the stack slots for the arguments and temporaries but leave one + // for the result. + fiber->stackTop = vm->apiStack + 1; + + vm->apiStack = NULL; +} + +// Handles the current fiber having aborted because of an error. +// +// Walks the call chain of fibers, aborting each one until it hits a fiber that +// handles the error. If none do, tells the VM to stop. +static void runtimeError(WrenVM* vm) { + ASSERT(wrenHasError(vm->fiber), "Should only call this after an error."); + + ObjFiber* current = vm->fiber; + Value error = current->error; + + while (current != NULL) { + // Every fiber along the call chain gets aborted with the same error. + current->error = error; + + // If the caller ran this fiber using "try", give it the error and stop. + if (current->state == FIBER_TRY) { + // Make the caller's try method return the error message. + current->caller->stackTop[-1] = vm->fiber->error; + vm->fiber = current->caller; + return; + } + + // Otherwise, unhook the caller since we will never resume and return to it. + ObjFiber* caller = current->caller; + current->caller = NULL; + current = caller; + } + + // If we got here, nothing caught the error, so show the stack trace. + wrenDebugPrintStackTrace(vm); + vm->fiber = NULL; + vm->apiStack = NULL; +} + +// Aborts the current fiber with an appropriate method not found error for a +// method with [symbol] on [classObj]. +static void methodNotFound(WrenVM* vm, ObjClass* classObj, int symbol) { + vm->fiber->error = wrenStringFormat(vm, "@ does not implement '$'.", OBJ_VAL(classObj->name), + vm->methodNames.data[symbol]->value); +} + +// Looks up the previously loaded module with [name]. +// +// Returns `NULL` if no module with that name has been loaded. +static ObjModule* getModule(WrenVM* vm, Value name) { + Value moduleValue = wrenMapGet(vm->modules, name); + return !IS_UNDEFINED(moduleValue) ? AS_MODULE(moduleValue) : NULL; +} + +static ObjClosure* compileInModule(WrenVM* vm, Value name, const char* source, bool isExpression, bool printErrors) { + // See if the module has already been loaded. + ObjModule* module = getModule(vm, name); + if (module == NULL) { + module = wrenNewModule(vm, AS_STRING(name)); + + // It's possible for the wrenMapSet below to resize the modules map, + // and trigger a GC while doing so. When this happens it will collect + // the module we've just created. Once in the map it is safe. + wrenPushRoot(vm, (Obj*)module); + + // Store it in the VM's module registry so we don't load the same module + // multiple times. + wrenMapSet(vm, vm->modules, name, OBJ_VAL(module)); + + wrenPopRoot(vm); + + // Implicitly import the core module. + ObjModule* coreModule = getModule(vm, NULL_VAL); + for (int i = 0; i < coreModule->variables.count; i++) { + wrenDefineVariable(vm, module, coreModule->variableNames.data[i]->value, + coreModule->variableNames.data[i]->length, coreModule->variables.data[i], NULL); + } + } + + ObjFn* fn = wrenCompile(vm, module, source, isExpression, printErrors); + if (fn == NULL) { + // TODO: Should we still store the module even if it didn't compile? + return NULL; + } + + // Functions are always wrapped in closures. + wrenPushRoot(vm, (Obj*)fn); + ObjClosure* closure = wrenNewClosure(vm, fn); + wrenPopRoot(vm); // fn. + + return closure; +} + +// Verifies that [superclassValue] is a valid object to inherit from. That +// means it must be a class and cannot be the class of any built-in type. +// +// Also validates that it doesn't result in a class with too many fields and +// the other limitations foreign classes have. +// +// If successful, returns `null`. Otherwise, returns a string for the runtime +// error message. +static Value validateSuperclass(WrenVM* vm, Value name, Value superclassValue, int numFields) { + // Make sure the superclass is a class. + if (!IS_CLASS(superclassValue)) { + return wrenStringFormat(vm, "Class '@' cannot inherit from a non-class object.", name); + } + + // Make sure it doesn't inherit from a sealed built-in type. Primitive methods + // on these classes assume the instance is one of the other Obj___ types and + // will fail horribly if it's actually an ObjInstance. + ObjClass* superclass = AS_CLASS(superclassValue); + if (superclass == vm->classClass || superclass == vm->fiberClass || + superclass == vm->fnClass || // Includes OBJ_CLOSURE. + superclass == vm->listClass || superclass == vm->mapClass || superclass == vm->rangeClass || + superclass == vm->stringClass || superclass == vm->boolClass || superclass == vm->nullClass || + superclass == vm->numClass) { + return wrenStringFormat(vm, "Class '@' cannot inherit from built-in class '@'.", name, + OBJ_VAL(superclass->name)); + } + + if (superclass->numFields == -1) { + return wrenStringFormat(vm, "Class '@' cannot inherit from foreign class '@'.", name, + OBJ_VAL(superclass->name)); + } + + if (numFields == -1 && superclass->numFields > 0) { + return wrenStringFormat(vm, "Foreign class '@' may not inherit from a class with fields.", name); + } + + if (superclass->numFields + numFields > MAX_FIELDS) { + return wrenStringFormat(vm, + "Class '@' may not have more than 255 fields, including inherited " + "ones.", + name); + } + + return NULL_VAL; +} + +static void bindForeignClass(WrenVM* vm, ObjClass* classObj, ObjModule* module) { + WrenForeignClassMethods methods; + methods.allocate = NULL; + methods.finalize = NULL; + + // Check the optional built-in module first so the host can override it. + + if (vm->config.bindForeignClassFn != NULL) { + methods = vm->config.bindForeignClassFn(vm, module->name->value, classObj->name->value); + } + + // If the host didn't provide it, see if it's a built in optional module. + if (methods.allocate == NULL && methods.finalize == NULL) { +#if WREN_OPT_RANDOM + if (strcmp(module->name->value, "random") == 0) { + methods = wrenRandomBindForeignClass(vm, module->name->value, classObj->name->value); + } +#endif + } + + Method method; + method.type = METHOD_FOREIGN; + + // Add the symbol even if there is no allocator so we can ensure that the + // symbol itself is always in the symbol table. + int symbol = wrenSymbolTableEnsure(vm, &vm->methodNames, "", 10); + if (methods.allocate != NULL) { + method.as.foreign = methods.allocate; + wrenBindMethod(vm, classObj, symbol, method); + } + + // Add the symbol even if there is no finalizer so we can ensure that the + // symbol itself is always in the symbol table. + symbol = wrenSymbolTableEnsure(vm, &vm->methodNames, "", 10); + if (methods.finalize != NULL) { + method.as.foreign = (WrenForeignMethodFn)methods.finalize; + wrenBindMethod(vm, classObj, symbol, method); + } +} + +// Completes the process for creating a new class. +// +// The class attributes instance and the class itself should be on the +// top of the fiber's stack. +// +// This process handles moving the attribute data for a class from +// compile time to runtime, since it now has all the attributes associated +// with a class, including for methods. +static void endClass(WrenVM* vm) { + // Pull the attributes and class off the stack + Value attributes = vm->fiber->stackTop[-2]; + Value classValue = vm->fiber->stackTop[-1]; + + // Remove the stack items + vm->fiber->stackTop -= 2; + + ObjClass* classObj = AS_CLASS(classValue); + classObj->attributes = attributes; +} + +// Creates a new class. +// +// If [numFields] is -1, the class is a foreign class. The name and superclass +// should be on top of the fiber's stack. After calling this, the top of the +// stack will contain the new class. +// +// Aborts the current fiber if an error occurs. +static void createClass(WrenVM* vm, int numFields, ObjModule* module) { + // Pull the name and superclass off the stack. + Value name = vm->fiber->stackTop[-2]; + Value superclass = vm->fiber->stackTop[-1]; + + // We have two values on the stack and we are going to leave one, so discard + // the other slot. + vm->fiber->stackTop--; + + vm->fiber->error = validateSuperclass(vm, name, superclass, numFields); + if (wrenHasError(vm->fiber)) return; + + ObjClass* classObj = wrenNewClass(vm, AS_CLASS(superclass), numFields, AS_STRING(name)); + vm->fiber->stackTop[-1] = OBJ_VAL(classObj); + + if (numFields == -1) bindForeignClass(vm, classObj, module); +} + +static void createForeign(WrenVM* vm, ObjFiber* fiber, Value* stack) { + ObjClass* classObj = AS_CLASS(stack[0]); + ASSERT(classObj->numFields == -1, "Class must be a foreign class."); + + // TODO: Don't look up every time. + int symbol = wrenSymbolTableFind(&vm->methodNames, "", 10); + ASSERT(symbol != -1, "Should have defined symbol."); + + ASSERT(classObj->methods.count > symbol, "Class should have allocator."); + Method* method = &classObj->methods.data[symbol]; + ASSERT(method->type == METHOD_FOREIGN, "Allocator should be foreign."); + + // Pass the constructor arguments to the allocator as well. + ASSERT(vm->apiStack == NULL, "Cannot already be in foreign call."); + vm->apiStack = stack; + + method->as.foreign(vm); + + vm->apiStack = NULL; +} + +void wrenFinalizeForeign(WrenVM* vm, ObjForeign* foreign) { + // TODO: Don't look up every time. + int symbol = wrenSymbolTableFind(&vm->methodNames, "", 10); + ASSERT(symbol != -1, "Should have defined symbol."); + + // If there are no finalizers, don't finalize it. + if (symbol == -1) return; + + // If the class doesn't have a finalizer, bail out. + ObjClass* classObj = foreign->obj.classObj; + if (symbol >= classObj->methods.count) return; + + Method* method = &classObj->methods.data[symbol]; + if (method->type == METHOD_NONE) return; + + ASSERT(method->type == METHOD_FOREIGN, "Finalizer should be foreign."); + + WrenFinalizerFn finalizer = (WrenFinalizerFn)method->as.foreign; + finalizer(foreign->data); +} + +// Let the host resolve an imported module name if it wants to. +static Value resolveModule(WrenVM* vm, Value name) { + // If the host doesn't care to resolve, leave the name alone. + if (vm->config.resolveModuleFn == NULL) return name; + + ObjFiber* fiber = vm->fiber; + ObjFn* fn = fiber->frames[fiber->numFrames - 1].closure->fn; + ObjString* importer = fn->module->name; + + const char* resolved = vm->config.resolveModuleFn(vm, importer->value, AS_CSTRING(name)); + if (resolved == NULL) { + vm->fiber->error = + wrenStringFormat(vm, "Could not resolve module '@' imported from '@'.", name, OBJ_VAL(importer)); + return NULL_VAL; + } + + // If they resolved to the exact same string, we don't need to copy it. + if (resolved == AS_CSTRING(name)) return name; + + // Copy the string into a Wren String object. + name = wrenNewString(vm, resolved); + DEALLOCATE(vm, (char*)resolved); + return name; +} + +static Value importModule(WrenVM* vm, Value name) { + name = resolveModule(vm, name); + + // If the module is already loaded, we don't need to do anything. + Value existing = wrenMapGet(vm->modules, name); + if (!IS_UNDEFINED(existing)) return existing; + + wrenPushRoot(vm, AS_OBJ(name)); + + WrenLoadModuleResult result = {0}; + const char* source = NULL; + + // Let the host try to provide the module. + if (vm->config.loadModuleFn != NULL) { + result = vm->config.loadModuleFn(vm, AS_CSTRING(name)); + } + + // If the host didn't provide it, see if it's a built in optional module. + if (result.source == NULL) { + result.onComplete = NULL; + ObjString* nameString = AS_STRING(name); +#if WREN_OPT_META + if (strcmp(nameString->value, "meta") == 0) result.source = wrenMetaSource(); +#endif +#if WREN_OPT_RANDOM + if (strcmp(nameString->value, "random") == 0) result.source = wrenRandomSource(); +#endif + } + + if (result.source == NULL) { + vm->fiber->error = wrenStringFormat(vm, "Could not load module '@'.", name); + wrenPopRoot(vm); // name. + return NULL_VAL; + } + + ObjClosure* moduleClosure = compileInModule(vm, name, result.source, false, true); + + // Now that we're done, give the result back in case there's cleanup to do. + if (result.onComplete) result.onComplete(vm, AS_CSTRING(name), result); + + if (moduleClosure == NULL) { + vm->fiber->error = wrenStringFormat(vm, "Could not compile module '@'.", name); + wrenPopRoot(vm); // name. + return NULL_VAL; + } + + wrenPopRoot(vm); // name. + + // Return the closure that executes the module. + return OBJ_VAL(moduleClosure); +} + +static Value getModuleVariable(WrenVM* vm, ObjModule* module, Value variableName) { + ObjString* variable = AS_STRING(variableName); + uint32_t variableEntry = wrenSymbolTableFind(&module->variableNames, variable->value, variable->length); + + // It's a runtime error if the imported variable does not exist. + if (variableEntry != UINT32_MAX) { + return module->variables.data[variableEntry]; + } + + vm->fiber->error = wrenStringFormat(vm, "Could not find a variable named '@' in module '@'.", variableName, + OBJ_VAL(module->name)); + return NULL_VAL; +} + +inline static bool checkArity(WrenVM* vm, Value value, int numArgs) { + ASSERT(IS_CLOSURE(value), "Receiver must be a closure."); + ObjFn* fn = AS_CLOSURE(value)->fn; + + // We only care about missing arguments, not extras. The "- 1" is because + // numArgs includes the receiver, the function itself, which we don't want to + // count. + if (numArgs - 1 >= fn->arity) return true; + + vm->fiber->error = CONST_STRING(vm, "Function expects more arguments."); + return false; +} + +// The main bytecode interpreter loop. This is where the magic happens. It is +// also, as you can imagine, highly performance critical. +static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber) { + // Remember the current fiber so we can find it if a GC happens. + vm->fiber = fiber; + fiber->state = FIBER_ROOT; + + // Hoist these into local variables. They are accessed frequently in the loop + // but assigned less frequently. Keeping them in locals and updating them when + // a call frame has been pushed or popped gives a large speed boost. + register CallFrame* frame; + register Value* stackStart; + register uint8_t* ip; + register ObjFn* fn; + +// These macros are designed to only be invoked within this function. +#define PUSH(value) (*fiber->stackTop++ = value) +#define POP() (*(--fiber->stackTop)) +#define DROP() (fiber->stackTop--) +#define PEEK() (*(fiber->stackTop - 1)) +#define PEEK2() (*(fiber->stackTop - 2)) +#define READ_BYTE() (*ip++) +#define READ_SHORT() (ip += 2, (uint16_t)((ip[-2] << 8) | ip[-1])) + +// Use this before a CallFrame is pushed to store the local variables back +// into the current one. +#define STORE_FRAME() frame->ip = ip + +// Use this after a CallFrame has been pushed or popped to refresh the local +// variables. +#define LOAD_FRAME() \ + do { \ + frame = &fiber->frames[fiber->numFrames - 1]; \ + stackStart = frame->stackStart; \ + ip = frame->ip; \ + fn = frame->closure->fn; \ + } while (false) + +// Terminates the current fiber with error string [error]. If another calling +// fiber is willing to catch the error, transfers control to it, otherwise +// exits the interpreter. +#define RUNTIME_ERROR() \ + do { \ + STORE_FRAME(); \ + runtimeError(vm); \ + if (vm->fiber == NULL) return WREN_RESULT_RUNTIME_ERROR; \ + fiber = vm->fiber; \ + LOAD_FRAME(); \ + DISPATCH(); \ + } while (false) + +#if WREN_DEBUG_TRACE_INSTRUCTIONS +// Prints the stack and instruction before each instruction is executed. +#define DEBUG_TRACE_INSTRUCTIONS() \ + do { \ + wrenDumpStack(fiber); \ + wrenDumpInstruction(vm, fn, (int)(ip - fn->code.data)); \ + } while (false) +#else +#define DEBUG_TRACE_INSTRUCTIONS() \ + do { \ + } while (false) +#endif + +#if WREN_COMPUTED_GOTO + + static void* dispatchTable[] = { +#define OPCODE(name, _) &&code_##name, + // Begin file "wren_opcodes.h" + // This defines the bytecode instructions used by the VM. It does so by invoking + // an OPCODE() macro which is expected to be defined at the point that this is + // included. (See: http://en.wikipedia.org/wiki/X_Macro for more.) + // + // The first argument is the name of the opcode. The second is its "stack + // effect" -- the amount that the op code changes the size of the stack. A + // stack effect of 1 means it pushes a value and the stack grows one larger. + // -2 means it pops two values, etc. + // + // Note that the order of instructions here affects the order of the dispatch + // table in the VM's interpreter loop. That in turn affects caching which + // affects overall performance. Take care to run benchmarks if you change the + // order here. + + // Load the constant at index [arg]. + OPCODE(CONSTANT, 1) + + // Push null onto the stack. + OPCODE(NULL, 1) + + // Push false onto the stack. + OPCODE(FALSE, 1) + + // Push true onto the stack. + OPCODE(TRUE, 1) + + // Pushes the value in the given local slot. + OPCODE(LOAD_LOCAL_0, 1) OPCODE(LOAD_LOCAL_1, 1) OPCODE(LOAD_LOCAL_2, 1) OPCODE(LOAD_LOCAL_3, 1) + OPCODE(LOAD_LOCAL_4, 1) OPCODE(LOAD_LOCAL_5, 1) OPCODE(LOAD_LOCAL_6, 1) OPCODE(LOAD_LOCAL_7, 1) + OPCODE(LOAD_LOCAL_8, 1) + + // Note: The compiler assumes the following _STORE instructions always + // immediately follow their corresponding _LOAD ones. + + // Pushes the value in local slot [arg]. + OPCODE(LOAD_LOCAL, 1) + + // Stores the top of stack in local slot [arg]. Does not pop it. + OPCODE(STORE_LOCAL, 0) + + // Pushes the value in upvalue [arg]. + OPCODE(LOAD_UPVALUE, 1) + + // Stores the top of stack in upvalue [arg]. Does not pop it. + OPCODE(STORE_UPVALUE, 0) + + // Pushes the value of the top-level variable in slot [arg]. + OPCODE(LOAD_MODULE_VAR, 1) + + // Stores the top of stack in top-level variable slot [arg]. Does not pop it. + OPCODE(STORE_MODULE_VAR, 0) + + // Pushes the value of the field in slot [arg] of the receiver of the current + // function. This is used for regular field accesses on "this" directly in + // methods. This instruction is faster than the more general CODE_LOAD_FIELD + // instruction. + OPCODE(LOAD_FIELD_THIS, 1) + + // Stores the top of the stack in field slot [arg] in the receiver of the + // current value. Does not pop the value. This instruction is faster than the + // more general CODE_LOAD_FIELD instruction. + OPCODE(STORE_FIELD_THIS, 0) + + // Pops an instance and pushes the value of the field in slot [arg] of it. + OPCODE(LOAD_FIELD, 0) + + // Pops an instance and stores the subsequent top of stack in field slot + // [arg] in it. Does not pop the value. + OPCODE(STORE_FIELD, -1) + + // Pop and discard the top of stack. + OPCODE(POP, -1) + + // Invoke the method with symbol [arg]. The number indicates the number of + // arguments (not including the receiver). + OPCODE(CALL_0, 0) OPCODE(CALL_1, -1) OPCODE(CALL_2, -2) OPCODE(CALL_3, -3) OPCODE(CALL_4, -4) + OPCODE(CALL_5, -5) OPCODE(CALL_6, -6) OPCODE(CALL_7, -7) OPCODE(CALL_8, -8) OPCODE(CALL_9, -9) + OPCODE(CALL_10, -10) OPCODE(CALL_11, -11) OPCODE(CALL_12, -12) OPCODE(CALL_13, -13) + OPCODE(CALL_14, -14) OPCODE(CALL_15, -15) OPCODE(CALL_16, -16) + + // Invoke a superclass method with symbol [arg]. The number indicates the + // number of arguments (not including the receiver). + OPCODE(SUPER_0, 0) OPCODE(SUPER_1, -1) OPCODE(SUPER_2, -2) OPCODE(SUPER_3, -3) OPCODE(SUPER_4, -4) + OPCODE(SUPER_5, -5) OPCODE(SUPER_6, -6) OPCODE(SUPER_7, -7) OPCODE(SUPER_8, -8) OPCODE(SUPER_9, -9) + OPCODE(SUPER_10, -10) OPCODE(SUPER_11, -11) OPCODE(SUPER_12, -12) OPCODE(SUPER_13, -13) + OPCODE(SUPER_14, -14) OPCODE(SUPER_15, -15) OPCODE(SUPER_16, -16) + + // Jump the instruction pointer [arg] forward. + OPCODE(JUMP, 0) + + // Jump the instruction pointer [arg] backward. + OPCODE(LOOP, 0) + + // Pop and if not truthy then jump the instruction pointer [arg] forward. + OPCODE(JUMP_IF, -1) + + // If the top of the stack is false, jump [arg] forward. Otherwise, pop and + // continue. + OPCODE(AND, -1) + + // If the top of the stack is non-false, jump [arg] forward. Otherwise, pop + // and continue. + OPCODE(OR, -1) + + // Close the upvalue for the local on the top of the stack, then pop it. + OPCODE(CLOSE_UPVALUE, -1) + + // Exit from the current function and return the value on the top of the + // stack. + OPCODE(RETURN, 0) + + // Creates a closure for the function stored at [arg] in the constant table. + // + // Following the function argument is a number of arguments, two for each + // upvalue. The first is true if the variable being captured is a local (as + // opposed to an upvalue), and the second is the index of the local or + // upvalue being captured. + // + // Pushes the created closure. + OPCODE(CLOSURE, 1) + + // Creates a new instance of a class. + // + // Assumes the class object is in slot zero, and replaces it with the new + // uninitialized instance of that class. This opcode is only emitted by the + // compiler-generated constructor metaclass methods. + OPCODE(CONSTRUCT, 0) + + // Creates a new instance of a foreign class. + // + // Assumes the class object is in slot zero, and replaces it with the new + // uninitialized instance of that class. This opcode is only emitted by the + // compiler-generated constructor metaclass methods. + OPCODE(FOREIGN_CONSTRUCT, 0) + + // Creates a class. Top of stack is the superclass. Below that is a string for + // the name of the class. Byte [arg] is the number of fields in the class. + OPCODE(CLASS, -1) + + // Ends a class. + // Atm the stack contains the class and the ClassAttributes (or null). + OPCODE(END_CLASS, -2) + + // Creates a foreign class. Top of stack is the superclass. Below that is a + // string for the name of the class. + OPCODE(FOREIGN_CLASS, -1) + + // Define a method for symbol [arg]. The class receiving the method is popped + // off the stack, then the function defining the body is popped. + // + // If a foreign method is being defined, the "function" will be a string + // identifying the foreign method. Otherwise, it will be a function or + // closure. + OPCODE(METHOD_INSTANCE, -2) + + // Define a method for symbol [arg]. The class whose metaclass will receive + // the method is popped off the stack, then the function defining the body is + // popped. + // + // If a foreign method is being defined, the "function" will be a string + // identifying the foreign method. Otherwise, it will be a function or + // closure. + OPCODE(METHOD_STATIC, -2) + + // This is executed at the end of the module's body. Pushes NULL onto the stack + // as the "return value" of the import statement and stores the module as the + // most recently imported one. + OPCODE(END_MODULE, 1) + + // Import a module whose name is the string stored at [arg] in the constant + // table. + // + // Pushes null onto the stack so that the fiber for the imported module can + // replace that with a dummy value when it returns. (Fibers always return a + // value when resuming a caller.) + OPCODE(IMPORT_MODULE, 1) + + // Import a variable from the most recently imported module. The name of the + // variable to import is at [arg] in the constant table. Pushes the loaded + // variable's value. + OPCODE(IMPORT_VARIABLE, 1) + + // This pseudo-instruction indicates the end of the bytecode. It should + // always be preceded by a `CODE_RETURN`, so is never actually executed. + OPCODE(END, 0) + // End file "wren_opcodes.h" +#undef OPCODE + }; + +#define INTERPRET_LOOP DISPATCH(); +#define CASE_CODE(name) code_##name + +#define DISPATCH() \ + do { \ + DEBUG_TRACE_INSTRUCTIONS(); \ + goto* dispatchTable[instruction = (Code)READ_BYTE()]; \ + } while (false) + +#else + +#define INTERPRET_LOOP \ + loop: \ + DEBUG_TRACE_INSTRUCTIONS(); \ + switch (instruction = (Code)READ_BYTE()) + +#define CASE_CODE(name) case CODE_##name +#define DISPATCH() goto loop + +#endif + + LOAD_FRAME(); + + Code instruction; + INTERPRET_LOOP { + CASE_CODE(LOAD_LOCAL_0) + : CASE_CODE(LOAD_LOCAL_1) + : CASE_CODE(LOAD_LOCAL_2) + : CASE_CODE(LOAD_LOCAL_3) + : CASE_CODE(LOAD_LOCAL_4) + : CASE_CODE(LOAD_LOCAL_5) + : CASE_CODE(LOAD_LOCAL_6) + : CASE_CODE(LOAD_LOCAL_7) : CASE_CODE(LOAD_LOCAL_8) : PUSH(stackStart[instruction - CODE_LOAD_LOCAL_0]); + DISPATCH(); + + CASE_CODE(LOAD_LOCAL) : PUSH(stackStart[READ_BYTE()]); + DISPATCH(); + + CASE_CODE(LOAD_FIELD_THIS) : { + uint8_t field = READ_BYTE(); + Value receiver = stackStart[0]; + ASSERT(IS_INSTANCE(receiver), "Receiver should be instance."); + ObjInstance* instance = AS_INSTANCE(receiver); + ASSERT(field < instance->obj.classObj->numFields, "Out of bounds field."); + PUSH(instance->fields[field]); + DISPATCH(); + } + + CASE_CODE(POP) : DROP(); + DISPATCH(); + CASE_CODE(NULL) : PUSH(NULL_VAL); + DISPATCH(); + CASE_CODE(FALSE) : PUSH(FALSE_VAL); + DISPATCH(); + CASE_CODE(TRUE) : PUSH(TRUE_VAL); + DISPATCH(); + + CASE_CODE(STORE_LOCAL) : stackStart[READ_BYTE()] = PEEK(); + DISPATCH(); + + CASE_CODE(CONSTANT) : PUSH(fn->constants.data[READ_SHORT()]); + DISPATCH(); + + { + // The opcodes for doing method and superclass calls share a lot of code. + // However, doing an if() test in the middle of the instruction sequence + // to handle the bit that is special to super calls makes the non-super + // call path noticeably slower. + // + // Instead, we do this old school using an explicit goto to share code for + // everything at the tail end of the call-handling code that is the same + // between normal and superclass calls. + int numArgs; + int symbol; + + Value* args; + ObjClass* classObj; + + Method* method; + + CASE_CODE(CALL_0) + : CASE_CODE(CALL_1) + : CASE_CODE(CALL_2) + : CASE_CODE(CALL_3) + : CASE_CODE(CALL_4) + : CASE_CODE(CALL_5) + : CASE_CODE(CALL_6) + : CASE_CODE(CALL_7) + : CASE_CODE(CALL_8) + : CASE_CODE(CALL_9) + : CASE_CODE(CALL_10) + : CASE_CODE(CALL_11) + : CASE_CODE(CALL_12) + : CASE_CODE(CALL_13) + : CASE_CODE(CALL_14) + : CASE_CODE(CALL_15) + : CASE_CODE(CALL_16) + : // Add one for the implicit receiver argument. + numArgs = instruction - CODE_CALL_0 + 1; + symbol = READ_SHORT(); + + // The receiver is the first argument. + args = fiber->stackTop - numArgs; + classObj = wrenGetClassInline(vm, args[0]); + goto completeCall; + + CASE_CODE(SUPER_0) + : CASE_CODE(SUPER_1) + : CASE_CODE(SUPER_2) + : CASE_CODE(SUPER_3) + : CASE_CODE(SUPER_4) + : CASE_CODE(SUPER_5) + : CASE_CODE(SUPER_6) + : CASE_CODE(SUPER_7) + : CASE_CODE(SUPER_8) + : CASE_CODE(SUPER_9) + : CASE_CODE(SUPER_10) + : CASE_CODE(SUPER_11) + : CASE_CODE(SUPER_12) + : CASE_CODE(SUPER_13) + : CASE_CODE(SUPER_14) + : CASE_CODE(SUPER_15) + : CASE_CODE(SUPER_16) + : // Add one for the implicit receiver argument. + numArgs = instruction - CODE_SUPER_0 + 1; + symbol = READ_SHORT(); + + // The receiver is the first argument. + args = fiber->stackTop - numArgs; + + // The superclass is stored in a constant. + classObj = AS_CLASS(fn->constants.data[READ_SHORT()]); + goto completeCall; + + completeCall: + // If the class's method table doesn't include the symbol, bail. + if (symbol >= classObj->methods.count || (method = &classObj->methods.data[symbol])->type == METHOD_NONE) { + methodNotFound(vm, classObj, symbol); + RUNTIME_ERROR(); + } + + switch (method->type) { + case METHOD_PRIMITIVE: + if (method->as.primitive(vm, args)) { + // The result is now in the first arg slot. Discard the other + // stack slots. + fiber->stackTop -= numArgs - 1; + } else { + // An error, fiber switch, or call frame change occurred. + STORE_FRAME(); + + // If we don't have a fiber to switch to, stop interpreting. + fiber = vm->fiber; + if (fiber == NULL) return WREN_RESULT_SUCCESS; + if (wrenHasError(fiber)) RUNTIME_ERROR(); + LOAD_FRAME(); + } + break; + + case METHOD_FUNCTION_CALL: + if (!checkArity(vm, args[0], numArgs)) { + RUNTIME_ERROR(); + break; + } + + STORE_FRAME(); + method->as.primitive(vm, args); + LOAD_FRAME(); + break; + + case METHOD_FOREIGN: + callForeign(vm, fiber, method->as.foreign, numArgs); + if (wrenHasError(fiber)) RUNTIME_ERROR(); + break; + + case METHOD_BLOCK: + STORE_FRAME(); + wrenCallFunction(vm, fiber, (ObjClosure*)method->as.closure, numArgs); + LOAD_FRAME(); + break; + + case METHOD_NONE: + UNREACHABLE(); + break; + } + DISPATCH(); + } + + CASE_CODE(LOAD_UPVALUE) : { + ObjUpvalue** upvalues = frame->closure->upvalues; + PUSH(*upvalues[READ_BYTE()]->value); + DISPATCH(); + } + + CASE_CODE(STORE_UPVALUE) : { + ObjUpvalue** upvalues = frame->closure->upvalues; + *upvalues[READ_BYTE()]->value = PEEK(); + DISPATCH(); + } + + CASE_CODE(LOAD_MODULE_VAR) : PUSH(fn->module->variables.data[READ_SHORT()]); + DISPATCH(); + + CASE_CODE(STORE_MODULE_VAR) : fn->module->variables.data[READ_SHORT()] = PEEK(); + DISPATCH(); + + CASE_CODE(STORE_FIELD_THIS) : { + uint8_t field = READ_BYTE(); + Value receiver = stackStart[0]; + ASSERT(IS_INSTANCE(receiver), "Receiver should be instance."); + ObjInstance* instance = AS_INSTANCE(receiver); + ASSERT(field < instance->obj.classObj->numFields, "Out of bounds field."); + instance->fields[field] = PEEK(); + DISPATCH(); + } + + CASE_CODE(LOAD_FIELD) : { + uint8_t field = READ_BYTE(); + Value receiver = POP(); + ASSERT(IS_INSTANCE(receiver), "Receiver should be instance."); + ObjInstance* instance = AS_INSTANCE(receiver); + ASSERT(field < instance->obj.classObj->numFields, "Out of bounds field."); + PUSH(instance->fields[field]); + DISPATCH(); + } + + CASE_CODE(STORE_FIELD) : { + uint8_t field = READ_BYTE(); + Value receiver = POP(); + ASSERT(IS_INSTANCE(receiver), "Receiver should be instance."); + ObjInstance* instance = AS_INSTANCE(receiver); + ASSERT(field < instance->obj.classObj->numFields, "Out of bounds field."); + instance->fields[field] = PEEK(); + DISPATCH(); + } + + CASE_CODE(JUMP) : { + uint16_t offset = READ_SHORT(); + ip += offset; + DISPATCH(); + } + + CASE_CODE(LOOP) : { + // Jump back to the top of the loop. + uint16_t offset = READ_SHORT(); + ip -= offset; + DISPATCH(); + } + + CASE_CODE(JUMP_IF) : { + uint16_t offset = READ_SHORT(); + Value condition = POP(); + + if (wrenIsFalsyValue(condition)) ip += offset; + DISPATCH(); + } + + CASE_CODE(AND) : { + uint16_t offset = READ_SHORT(); + Value condition = PEEK(); + + if (wrenIsFalsyValue(condition)) { + // Short-circuit the right hand side. + ip += offset; + } else { + // Discard the condition and evaluate the right hand side. + DROP(); + } + DISPATCH(); + } + + CASE_CODE(OR) : { + uint16_t offset = READ_SHORT(); + Value condition = PEEK(); + + if (wrenIsFalsyValue(condition)) { + // Discard the condition and evaluate the right hand side. + DROP(); + } else { + // Short-circuit the right hand side. + ip += offset; + } + DISPATCH(); + } + + CASE_CODE(CLOSE_UPVALUE) + : // Close the upvalue for the local if we have one. + closeUpvalues(fiber, fiber->stackTop - 1); + DROP(); + DISPATCH(); + + CASE_CODE(RETURN) : { + Value result = POP(); + fiber->numFrames--; + + // Close any upvalues still in scope. + closeUpvalues(fiber, stackStart); + + // If the fiber is complete, end it. + if (fiber->numFrames == 0) { + // See if there's another fiber to return to. If not, we're done. + if (fiber->caller == NULL) { + // Store the final result value at the beginning of the stack so the + // C API can get it. + fiber->stack[0] = result; + fiber->stackTop = fiber->stack + 1; + return WREN_RESULT_SUCCESS; + } + + ObjFiber* resumingFiber = fiber->caller; + fiber->caller = NULL; + fiber = resumingFiber; + vm->fiber = resumingFiber; + + // Store the result in the resuming fiber. + fiber->stackTop[-1] = result; + } else { + // Store the result of the block in the first slot, which is where the + // caller expects it. + stackStart[0] = result; + + // Discard the stack slots for the call frame (leaving one slot for the + // result). + fiber->stackTop = frame->stackStart + 1; + } + + LOAD_FRAME(); + DISPATCH(); + } + + CASE_CODE(CONSTRUCT) : ASSERT(IS_CLASS(stackStart[0]), "'this' should be a class."); + stackStart[0] = wrenNewInstance(vm, AS_CLASS(stackStart[0])); + DISPATCH(); + + CASE_CODE(FOREIGN_CONSTRUCT) : ASSERT(IS_CLASS(stackStart[0]), "'this' should be a class."); + createForeign(vm, fiber, stackStart); + if (wrenHasError(fiber)) RUNTIME_ERROR(); + DISPATCH(); + + CASE_CODE(CLOSURE) : { + // Create the closure and push it on the stack before creating upvalues + // so that it doesn't get collected. + ObjFn* function = AS_FN(fn->constants.data[READ_SHORT()]); + ObjClosure* closure = wrenNewClosure(vm, function); + PUSH(OBJ_VAL(closure)); + + // Capture upvalues, if any. + for (int i = 0; i < function->numUpvalues; i++) { + uint8_t isLocal = READ_BYTE(); + uint8_t index = READ_BYTE(); + if (isLocal) { + // Make an new upvalue to close over the parent's local variable. + closure->upvalues[i] = captureUpvalue(vm, fiber, frame->stackStart + index); + } else { + // Use the same upvalue as the current call frame. + closure->upvalues[i] = frame->closure->upvalues[index]; + } + } + DISPATCH(); + } + + CASE_CODE(END_CLASS) : { + endClass(vm); + if (wrenHasError(fiber)) RUNTIME_ERROR(); + DISPATCH(); + } + + CASE_CODE(CLASS) : { + createClass(vm, READ_BYTE(), NULL); + if (wrenHasError(fiber)) RUNTIME_ERROR(); + DISPATCH(); + } + + CASE_CODE(FOREIGN_CLASS) : { + createClass(vm, -1, fn->module); + if (wrenHasError(fiber)) RUNTIME_ERROR(); + DISPATCH(); + } + + CASE_CODE(METHOD_INSTANCE) : CASE_CODE(METHOD_STATIC) : { + uint16_t symbol = READ_SHORT(); + ObjClass* classObj = AS_CLASS(PEEK()); + Value method = PEEK2(); + bindMethod(vm, instruction, symbol, fn->module, classObj, method); + if (wrenHasError(fiber)) RUNTIME_ERROR(); + DROP(); + DROP(); + DISPATCH(); + } + + CASE_CODE(END_MODULE) : { + vm->lastModule = fn->module; + PUSH(NULL_VAL); + DISPATCH(); + } + + CASE_CODE(IMPORT_MODULE) : { + // Make a slot on the stack for the module's fiber to place the return + // value. It will be popped after this fiber is resumed. Store the + // imported module's closure in the slot in case a GC happens when + // invoking the closure. + PUSH(importModule(vm, fn->constants.data[READ_SHORT()])); + if (wrenHasError(fiber)) RUNTIME_ERROR(); + + // If we get a closure, call it to execute the module body. + if (IS_CLOSURE(PEEK())) { + STORE_FRAME(); + ObjClosure* closure = AS_CLOSURE(PEEK()); + wrenCallFunction(vm, fiber, closure, 1); + LOAD_FRAME(); + } else { + // The module has already been loaded. Remember it so we can import + // variables from it if needed. + vm->lastModule = AS_MODULE(PEEK()); + } + + DISPATCH(); + } + + CASE_CODE(IMPORT_VARIABLE) : { + Value variable = fn->constants.data[READ_SHORT()]; + ASSERT(vm->lastModule != NULL, "Should have already imported module."); + Value result = getModuleVariable(vm, vm->lastModule, variable); + if (wrenHasError(fiber)) RUNTIME_ERROR(); + + PUSH(result); + DISPATCH(); + } + + CASE_CODE(END) + : // A CODE_END should always be preceded by a CODE_RETURN. If we get here, + // the compiler generated wrong code. + UNREACHABLE(); + } + + // We should only exit this function from an explicit return from CODE_RETURN + // or a runtime error. + UNREACHABLE(); + return WREN_RESULT_RUNTIME_ERROR; + +#undef READ_BYTE +#undef READ_SHORT +} + +WrenHandle* wrenMakeCallHandle(WrenVM* vm, const char* signature) { + ASSERT(signature != NULL, "Signature cannot be NULL."); + + int signatureLength = (int)strlen(signature); + ASSERT(signatureLength > 0, "Signature cannot be empty."); + + // Count the number parameters the method expects. + int numParams = 0; + if (signature[signatureLength - 1] == ')') { + for (int i = signatureLength - 1; i > 0 && signature[i] != '('; i--) { + if (signature[i] == '_') numParams++; + } + } + + // Count subscript arguments. + if (signature[0] == '[') { + for (int i = 0; i < signatureLength && signature[i] != ']'; i++) { + if (signature[i] == '_') numParams++; + } + } + + // Add the signatue to the method table. + int method = wrenSymbolTableEnsure(vm, &vm->methodNames, signature, signatureLength); + + // Create a little stub function that assumes the arguments are on the stack + // and calls the method. + ObjFn* fn = wrenNewFunction(vm, NULL, numParams + 1); + + // Wrap the function in a closure and then in a handle. Do this here so it + // doesn't get collected as we fill it in. + WrenHandle* value = wrenMakeHandle(vm, OBJ_VAL(fn)); + value->value = OBJ_VAL(wrenNewClosure(vm, fn)); + + wrenByteBufferWrite(vm, &fn->code, (uint8_t)(CODE_CALL_0 + numParams)); + wrenByteBufferWrite(vm, &fn->code, (method >> 8) & 0xff); + wrenByteBufferWrite(vm, &fn->code, method & 0xff); + wrenByteBufferWrite(vm, &fn->code, CODE_RETURN); + wrenByteBufferWrite(vm, &fn->code, CODE_END); + wrenIntBufferFill(vm, &fn->debug->sourceLines, 0, 5); + wrenFunctionBindName(vm, fn, signature, signatureLength); + + return value; +} + +WrenInterpretResult wrenCall(WrenVM* vm, WrenHandle* method) { + ASSERT(method != NULL, "Method cannot be NULL."); + ASSERT(IS_CLOSURE(method->value), "Method must be a method handle."); + ASSERT(vm->fiber != NULL, "Must set up arguments for call first."); + ASSERT(vm->apiStack != NULL, "Must set up arguments for call first."); + ASSERT(vm->fiber->numFrames == 0, "Can not call from a foreign method."); + + ObjClosure* closure = AS_CLOSURE(method->value); + + ASSERT(vm->fiber->stackTop - vm->fiber->stack >= closure->fn->arity, + "Stack must have enough arguments for method."); + + // Clear the API stack. Now that wrenCall() has control, we no longer need + // it. We use this being non-null to tell if re-entrant calls to foreign + // methods are happening, so it's important to clear it out now so that you + // can call foreign methods from within calls to wrenCall(). + vm->apiStack = NULL; + + // Discard any extra temporary slots. We take for granted that the stub + // function has exactly one slot for each argument. + vm->fiber->stackTop = &vm->fiber->stack[closure->fn->maxSlots]; + + wrenCallFunction(vm, vm->fiber, closure, 0); + WrenInterpretResult result = runInterpreter(vm, vm->fiber); + + // If the call didn't abort, then set up the API stack to point to the + // beginning of the stack so the host can access the call's return value. + if (vm->fiber != NULL) vm->apiStack = vm->fiber->stack; + + return result; +} + +WrenHandle* wrenMakeHandle(WrenVM* vm, Value value) { + if (IS_OBJ(value)) wrenPushRoot(vm, AS_OBJ(value)); + + // Make a handle for it. + WrenHandle* handle = ALLOCATE(vm, WrenHandle); + handle->value = value; + + if (IS_OBJ(value)) wrenPopRoot(vm); + + // Add it to the front of the linked list of handles. + if (vm->handles != NULL) vm->handles->prev = handle; + handle->prev = NULL; + handle->next = vm->handles; + vm->handles = handle; + + return handle; +} + +void wrenReleaseHandle(WrenVM* vm, WrenHandle* handle) { + ASSERT(handle != NULL, "Handle cannot be NULL."); + + // Update the VM's head pointer if we're releasing the first handle. + if (vm->handles == handle) vm->handles = handle->next; + + // Unlink it from the list. + if (handle->prev != NULL) handle->prev->next = handle->next; + if (handle->next != NULL) handle->next->prev = handle->prev; + + // Clear it out. This isn't strictly necessary since we're going to free it, + // but it makes for easier debugging. + handle->prev = NULL; + handle->next = NULL; + handle->value = NULL_VAL; + DEALLOCATE(vm, handle); +} + +WrenInterpretResult wrenInterpret(WrenVM* vm, const char* module, const char* source) { + ObjClosure* closure = wrenCompileSource(vm, module, source, false, true); + if (closure == NULL) return WREN_RESULT_COMPILE_ERROR; + + wrenPushRoot(vm, (Obj*)closure); + ObjFiber* fiber = wrenNewFiber(vm, closure); + wrenPopRoot(vm); // closure. + vm->apiStack = NULL; + + return runInterpreter(vm, fiber); +} + +ObjClosure* wrenCompileSource(WrenVM* vm, const char* module, const char* source, bool isExpression, bool printErrors) { + Value nameValue = NULL_VAL; + if (module != NULL) { + nameValue = wrenNewString(vm, module); + wrenPushRoot(vm, AS_OBJ(nameValue)); + } + + ObjClosure* closure = compileInModule(vm, nameValue, source, isExpression, printErrors); + + if (module != NULL) wrenPopRoot(vm); // nameValue. + return closure; +} + +Value wrenGetModuleVariable(WrenVM* vm, Value moduleName, Value variableName) { + ObjModule* module = getModule(vm, moduleName); + if (module == NULL) { + vm->fiber->error = wrenStringFormat(vm, "Module '@' is not loaded.", moduleName); + return NULL_VAL; + } + + return getModuleVariable(vm, module, variableName); +} + +Value wrenFindVariable(WrenVM* vm, ObjModule* module, const char* name) { + int symbol = wrenSymbolTableFind(&module->variableNames, name, strlen(name)); + return module->variables.data[symbol]; +} + +int wrenDeclareVariable(WrenVM* vm, ObjModule* module, const char* name, size_t length, int line) { + if (module->variables.count == MAX_MODULE_VARS) return -2; + + // Implicitly defined variables get a "value" that is the line where the + // variable is first used. We'll use that later to report an error on the + // right line. + wrenValueBufferWrite(vm, &module->variables, NUM_VAL(line)); + return wrenSymbolTableAdd(vm, &module->variableNames, name, length); +} + +int wrenDefineVariable(WrenVM* vm, ObjModule* module, const char* name, size_t length, Value value, int* line) { + if (module->variables.count == MAX_MODULE_VARS) return -2; + + if (IS_OBJ(value)) wrenPushRoot(vm, AS_OBJ(value)); + + // See if the variable is already explicitly or implicitly declared. + int symbol = wrenSymbolTableFind(&module->variableNames, name, length); + + if (symbol == -1) { + // Brand new variable. + symbol = wrenSymbolTableAdd(vm, &module->variableNames, name, length); + wrenValueBufferWrite(vm, &module->variables, value); + } else if (IS_NUM(module->variables.data[symbol])) { + // An implicitly declared variable's value will always be a number. + // Now we have a real definition. + if (line) *line = (int)AS_NUM(module->variables.data[symbol]); + module->variables.data[symbol] = value; + + // If this was a localname we want to error if it was + // referenced before this definition. + if (wrenIsLocalName(name)) symbol = -3; + } else { + // Already explicitly declared. + symbol = -1; + } + + if (IS_OBJ(value)) wrenPopRoot(vm); + + return symbol; +} + +// TODO: Inline? +void wrenPushRoot(WrenVM* vm, Obj* obj) { + ASSERT(obj != NULL, "Can't root NULL."); + ASSERT(vm->numTempRoots < WREN_MAX_TEMP_ROOTS, "Too many temporary roots."); + + vm->tempRoots[vm->numTempRoots++] = obj; +} + +void wrenPopRoot(WrenVM* vm) { + ASSERT(vm->numTempRoots > 0, "No temporary roots to release."); + vm->numTempRoots--; +} + +int wrenGetSlotCount(WrenVM* vm) { + if (vm->apiStack == NULL) return 0; + + return (int)(vm->fiber->stackTop - vm->apiStack); +} + +void wrenEnsureSlots(WrenVM* vm, int numSlots) { + // If we don't have a fiber accessible, create one for the API to use. + if (vm->apiStack == NULL) { + vm->fiber = wrenNewFiber(vm, NULL); + vm->apiStack = vm->fiber->stack; + } + + int currentSize = (int)(vm->fiber->stackTop - vm->apiStack); + if (currentSize >= numSlots) return; + + // Grow the stack if needed. + int needed = (int)(vm->apiStack - vm->fiber->stack) + numSlots; + wrenEnsureStack(vm, vm->fiber, needed); + + vm->fiber->stackTop = vm->apiStack + numSlots; +} + +// Ensures that [slot] is a valid index into the API's stack of slots. +static void validateApiSlot(WrenVM* vm, int slot) { + ASSERT(slot >= 0, "Slot cannot be negative."); + ASSERT(slot < wrenGetSlotCount(vm), "Not that many slots."); +} + +// Gets the type of the object in [slot]. +WrenType wrenGetSlotType(WrenVM* vm, int slot) { + validateApiSlot(vm, slot); + if (IS_BOOL(vm->apiStack[slot])) return WREN_TYPE_BOOL; + if (IS_NUM(vm->apiStack[slot])) return WREN_TYPE_NUM; + if (IS_FOREIGN(vm->apiStack[slot])) return WREN_TYPE_FOREIGN; + if (IS_LIST(vm->apiStack[slot])) return WREN_TYPE_LIST; + if (IS_MAP(vm->apiStack[slot])) return WREN_TYPE_MAP; + if (IS_NULL(vm->apiStack[slot])) return WREN_TYPE_NULL; + if (IS_STRING(vm->apiStack[slot])) return WREN_TYPE_STRING; + + return WREN_TYPE_UNKNOWN; +} + +bool wrenGetSlotBool(WrenVM* vm, int slot) { + validateApiSlot(vm, slot); + ASSERT(IS_BOOL(vm->apiStack[slot]), "Slot must hold a bool."); + + return AS_BOOL(vm->apiStack[slot]); +} + +const char* wrenGetSlotBytes(WrenVM* vm, int slot, int* length) { + validateApiSlot(vm, slot); + ASSERT(IS_STRING(vm->apiStack[slot]), "Slot must hold a string."); + + ObjString* string = AS_STRING(vm->apiStack[slot]); + *length = string->length; + return string->value; +} + +double wrenGetSlotDouble(WrenVM* vm, int slot) { + validateApiSlot(vm, slot); + ASSERT(IS_NUM(vm->apiStack[slot]), "Slot must hold a number."); + + return AS_NUM(vm->apiStack[slot]); +} + +void* wrenGetSlotForeign(WrenVM* vm, int slot) { + validateApiSlot(vm, slot); + ASSERT(IS_FOREIGN(vm->apiStack[slot]), "Slot must hold a foreign instance."); + + return AS_FOREIGN(vm->apiStack[slot])->data; +} + +const char* wrenGetSlotString(WrenVM* vm, int slot) { + validateApiSlot(vm, slot); + ASSERT(IS_STRING(vm->apiStack[slot]), "Slot must hold a string."); + + return AS_CSTRING(vm->apiStack[slot]); +} + +WrenHandle* wrenGetSlotHandle(WrenVM* vm, int slot) { + validateApiSlot(vm, slot); + return wrenMakeHandle(vm, vm->apiStack[slot]); +} + +// Stores [value] in [slot] in the foreign call stack. +static void setSlot(WrenVM* vm, int slot, Value value) { + validateApiSlot(vm, slot); + vm->apiStack[slot] = value; +} + +void wrenSetSlotBool(WrenVM* vm, int slot, bool value) { + setSlot(vm, slot, BOOL_VAL(value)); +} + +void wrenSetSlotBytes(WrenVM* vm, int slot, const char* bytes, size_t length) { + ASSERT(bytes != NULL, "Byte array cannot be NULL."); + setSlot(vm, slot, wrenNewStringLength(vm, bytes, length)); +} + +void wrenSetSlotDouble(WrenVM* vm, int slot, double value) { + setSlot(vm, slot, NUM_VAL(value)); +} + +void* wrenSetSlotNewForeign(WrenVM* vm, int slot, int classSlot, size_t size) { + validateApiSlot(vm, slot); + validateApiSlot(vm, classSlot); + ASSERT(IS_CLASS(vm->apiStack[classSlot]), "Slot must hold a class."); + + ObjClass* classObj = AS_CLASS(vm->apiStack[classSlot]); + ASSERT(classObj->numFields == -1, "Class must be a foreign class."); + + ObjForeign* foreign = wrenNewForeign(vm, classObj, size); + vm->apiStack[slot] = OBJ_VAL(foreign); + + return (void*)foreign->data; +} + +void wrenSetSlotNewList(WrenVM* vm, int slot) { + setSlot(vm, slot, OBJ_VAL(wrenNewList(vm, 0))); +} + +void wrenSetSlotNewMap(WrenVM* vm, int slot) { + setSlot(vm, slot, OBJ_VAL(wrenNewMap(vm))); +} + +void wrenSetSlotNull(WrenVM* vm, int slot) { + setSlot(vm, slot, NULL_VAL); +} + +void wrenSetSlotString(WrenVM* vm, int slot, const char* text) { + ASSERT(text != NULL, "String cannot be NULL."); + + setSlot(vm, slot, wrenNewString(vm, text)); +} + +void wrenSetSlotHandle(WrenVM* vm, int slot, WrenHandle* handle) { + ASSERT(handle != NULL, "Handle cannot be NULL."); + + setSlot(vm, slot, handle->value); +} + +int wrenGetListCount(WrenVM* vm, int slot) { + validateApiSlot(vm, slot); + ASSERT(IS_LIST(vm->apiStack[slot]), "Slot must hold a list."); + + ValueBuffer elements = AS_LIST(vm->apiStack[slot])->elements; + return elements.count; +} + +void wrenGetListElement(WrenVM* vm, int listSlot, int index, int elementSlot) { + validateApiSlot(vm, listSlot); + validateApiSlot(vm, elementSlot); + ASSERT(IS_LIST(vm->apiStack[listSlot]), "Slot must hold a list."); + + ValueBuffer elements = AS_LIST(vm->apiStack[listSlot])->elements; + + uint32_t usedIndex = wrenValidateIndex(elements.count, index); + ASSERT(usedIndex != UINT32_MAX, "Index out of bounds."); + + vm->apiStack[elementSlot] = elements.data[usedIndex]; +} + +void wrenSetListElement(WrenVM* vm, int listSlot, int index, int elementSlot) { + validateApiSlot(vm, listSlot); + validateApiSlot(vm, elementSlot); + ASSERT(IS_LIST(vm->apiStack[listSlot]), "Slot must hold a list."); + + ObjList* list = AS_LIST(vm->apiStack[listSlot]); + + uint32_t usedIndex = wrenValidateIndex(list->elements.count, index); + ASSERT(usedIndex != UINT32_MAX, "Index out of bounds."); + + list->elements.data[usedIndex] = vm->apiStack[elementSlot]; +} + +void wrenInsertInList(WrenVM* vm, int listSlot, int index, int elementSlot) { + validateApiSlot(vm, listSlot); + validateApiSlot(vm, elementSlot); + ASSERT(IS_LIST(vm->apiStack[listSlot]), "Must insert into a list."); + + ObjList* list = AS_LIST(vm->apiStack[listSlot]); + + // Negative indices count from the end. + // We don't use wrenValidateIndex here because insert allows 1 past the end. + if (index < 0) index = list->elements.count + 1 + index; + + ASSERT(index <= list->elements.count, "Index out of bounds."); + + wrenListInsert(vm, list, vm->apiStack[elementSlot], index); +} + +int wrenGetMapCount(WrenVM* vm, int slot) { + validateApiSlot(vm, slot); + ASSERT(IS_MAP(vm->apiStack[slot]), "Slot must hold a map."); + + ObjMap* map = AS_MAP(vm->apiStack[slot]); + return map->count; +} + +bool wrenGetMapContainsKey(WrenVM* vm, int mapSlot, int keySlot) { + validateApiSlot(vm, mapSlot); + validateApiSlot(vm, keySlot); + ASSERT(IS_MAP(vm->apiStack[mapSlot]), "Slot must hold a map."); + + Value key = vm->apiStack[keySlot]; + ASSERT(wrenMapIsValidKey(key), "Key must be a value type"); + if (!validateKey(vm, key)) return false; + + ObjMap* map = AS_MAP(vm->apiStack[mapSlot]); + Value value = wrenMapGet(map, key); + + return !IS_UNDEFINED(value); +} + +void wrenGetMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot) { + validateApiSlot(vm, mapSlot); + validateApiSlot(vm, keySlot); + validateApiSlot(vm, valueSlot); + ASSERT(IS_MAP(vm->apiStack[mapSlot]), "Slot must hold a map."); + + ObjMap* map = AS_MAP(vm->apiStack[mapSlot]); + Value value = wrenMapGet(map, vm->apiStack[keySlot]); + if (IS_UNDEFINED(value)) { + value = NULL_VAL; + } + + vm->apiStack[valueSlot] = value; +} + +void wrenSetMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot) { + validateApiSlot(vm, mapSlot); + validateApiSlot(vm, keySlot); + validateApiSlot(vm, valueSlot); + ASSERT(IS_MAP(vm->apiStack[mapSlot]), "Must insert into a map."); + + Value key = vm->apiStack[keySlot]; + ASSERT(wrenMapIsValidKey(key), "Key must be a value type"); + + if (!validateKey(vm, key)) { + return; + } + + Value value = vm->apiStack[valueSlot]; + ObjMap* map = AS_MAP(vm->apiStack[mapSlot]); + + wrenMapSet(vm, map, key, value); +} + +void wrenRemoveMapValue(WrenVM* vm, int mapSlot, int keySlot, int removedValueSlot) { + validateApiSlot(vm, mapSlot); + validateApiSlot(vm, keySlot); + ASSERT(IS_MAP(vm->apiStack[mapSlot]), "Slot must hold a map."); + + Value key = vm->apiStack[keySlot]; + if (!validateKey(vm, key)) { + return; + } + + ObjMap* map = AS_MAP(vm->apiStack[mapSlot]); + Value removed = wrenMapRemoveKey(vm, map, key); + setSlot(vm, removedValueSlot, removed); +} + +void wrenGetVariable(WrenVM* vm, const char* module, const char* name, int slot) { + ASSERT(module != NULL, "Module cannot be NULL."); + ASSERT(name != NULL, "Variable name cannot be NULL."); + + Value moduleName = wrenStringFormat(vm, "$", module); + wrenPushRoot(vm, AS_OBJ(moduleName)); + + ObjModule* moduleObj = getModule(vm, moduleName); + ASSERT(moduleObj != NULL, "Could not find module."); + + wrenPopRoot(vm); // moduleName. + + int variableSlot = wrenSymbolTableFind(&moduleObj->variableNames, name, strlen(name)); + ASSERT(variableSlot != -1, "Could not find variable."); + + setSlot(vm, slot, moduleObj->variables.data[variableSlot]); +} + +bool wrenHasVariable(WrenVM* vm, const char* module, const char* name) { + ASSERT(module != NULL, "Module cannot be NULL."); + ASSERT(name != NULL, "Variable name cannot be NULL."); + + Value moduleName = wrenStringFormat(vm, "$", module); + wrenPushRoot(vm, AS_OBJ(moduleName)); + + //We don't use wrenHasModule since we want to use the module object. + ObjModule* moduleObj = getModule(vm, moduleName); + ASSERT(moduleObj != NULL, "Could not find module."); + + wrenPopRoot(vm); // moduleName. + + int variableSlot = wrenSymbolTableFind(&moduleObj->variableNames, name, strlen(name)); + + return variableSlot != -1; +} + +bool wrenHasModule(WrenVM* vm, const char* module) { + ASSERT(module != NULL, "Module cannot be NULL."); + + Value moduleName = wrenStringFormat(vm, "$", module); + wrenPushRoot(vm, AS_OBJ(moduleName)); + + ObjModule* moduleObj = getModule(vm, moduleName); + + wrenPopRoot(vm); // moduleName. + + return moduleObj != NULL; +} + +void wrenAbortFiber(WrenVM* vm, int slot) { + validateApiSlot(vm, slot); + vm->fiber->error = vm->apiStack[slot]; +} + +void* wrenGetUserData(WrenVM* vm) { + return vm->config.userData; +} + +void wrenSetUserData(WrenVM* vm, void* userData) { + vm->config.userData = userData; +} +// End file "wren_vm.c" +// Begin file "wren_core.c" +#include +#include +#include +#include +#include +#include + +// Begin file "wren_core.wren.inc" +// Generated automatically from src/vm/wren_core.wren. Do not edit. +static const char* coreModuleSource = + "class Bool {}\n" + "class Fiber {}\n" + "class Fn {}\n" + "class Null {}\n" + "class Num {}\n" + "\n" + "class Sequence {\n" + " all(f) {\n" + " var result = true\n" + " for (element in this) {\n" + " result = f.call(element)\n" + " if (!result) return result\n" + " }\n" + " return result\n" + " }\n" + "\n" + " any(f) {\n" + " var result = false\n" + " for (element in this) {\n" + " result = f.call(element)\n" + " if (result) return result\n" + " }\n" + " return result\n" + " }\n" + "\n" + " contains(element) {\n" + " for (item in this) {\n" + " if (element == item) return true\n" + " }\n" + " return false\n" + " }\n" + "\n" + " count {\n" + " var result = 0\n" + " for (element in this) {\n" + " result = result + 1\n" + " }\n" + " return result\n" + " }\n" + "\n" + " count(f) {\n" + " var result = 0\n" + " for (element in this) {\n" + " if (f.call(element)) result = result + 1\n" + " }\n" + " return result\n" + " }\n" + "\n" + " each(f) {\n" + " for (element in this) {\n" + " f.call(element)\n" + " }\n" + " }\n" + "\n" + " isEmpty { iterate(null) ? false : true }\n" + "\n" + " map(transformation) { MapSequence.new(this, transformation) }\n" + "\n" + " skip(count) {\n" + " if (!(count is Num) || !count.isInteger || count < 0) {\n" + " Fiber.abort(\"Count must be a non-negative integer.\")\n" + " }\n" + "\n" + " return SkipSequence.new(this, count)\n" + " }\n" + "\n" + " take(count) {\n" + " if (!(count is Num) || !count.isInteger || count < 0) {\n" + " Fiber.abort(\"Count must be a non-negative integer.\")\n" + " }\n" + "\n" + " return TakeSequence.new(this, count)\n" + " }\n" + "\n" + " where(predicate) { WhereSequence.new(this, predicate) }\n" + "\n" + " reduce(acc, f) {\n" + " for (element in this) {\n" + " acc = f.call(acc, element)\n" + " }\n" + " return acc\n" + " }\n" + "\n" + " reduce(f) {\n" + " var iter = iterate(null)\n" + " if (!iter) Fiber.abort(\"Can't reduce an empty sequence.\")\n" + "\n" + " // Seed with the first element.\n" + " var result = iteratorValue(iter)\n" + " while (iter = iterate(iter)) {\n" + " result = f.call(result, iteratorValue(iter))\n" + " }\n" + "\n" + " return result\n" + " }\n" + "\n" + " join() { join(\"\") }\n" + "\n" + " join(sep) {\n" + " var first = true\n" + " var result = \"\"\n" + "\n" + " for (element in this) {\n" + " if (!first) result = result + sep\n" + " first = false\n" + " result = result + element.toString\n" + " }\n" + "\n" + " return result\n" + " }\n" + "\n" + " toList {\n" + " var result = List.new()\n" + " for (element in this) {\n" + " result.add(element)\n" + " }\n" + " return result\n" + " }\n" + "}\n" + "\n" + "class MapSequence is Sequence {\n" + " construct new(sequence, fn) {\n" + " _sequence = sequence\n" + " _fn = fn\n" + " }\n" + "\n" + " iterate(iterator) { _sequence.iterate(iterator) }\n" + " iteratorValue(iterator) { _fn.call(_sequence.iteratorValue(iterator)) }\n" + "}\n" + "\n" + "class SkipSequence is Sequence {\n" + " construct new(sequence, count) {\n" + " _sequence = sequence\n" + " _count = count\n" + " }\n" + "\n" + " iterate(iterator) {\n" + " if (iterator) {\n" + " return _sequence.iterate(iterator)\n" + " } else {\n" + " iterator = _sequence.iterate(iterator)\n" + " var count = _count\n" + " while (count > 0 && iterator) {\n" + " iterator = _sequence.iterate(iterator)\n" + " count = count - 1\n" + " }\n" + " return iterator\n" + " }\n" + " }\n" + "\n" + " iteratorValue(iterator) { _sequence.iteratorValue(iterator) }\n" + "}\n" + "\n" + "class TakeSequence is Sequence {\n" + " construct new(sequence, count) {\n" + " _sequence = sequence\n" + " _count = count\n" + " }\n" + "\n" + " iterate(iterator) {\n" + " if (!iterator) _taken = 1 else _taken = _taken + 1\n" + " return _taken > _count ? null : _sequence.iterate(iterator)\n" + " }\n" + "\n" + " iteratorValue(iterator) { _sequence.iteratorValue(iterator) }\n" + "}\n" + "\n" + "class WhereSequence is Sequence {\n" + " construct new(sequence, fn) {\n" + " _sequence = sequence\n" + " _fn = fn\n" + " }\n" + "\n" + " iterate(iterator) {\n" + " while (iterator = _sequence.iterate(iterator)) {\n" + " if (_fn.call(_sequence.iteratorValue(iterator))) break\n" + " }\n" + " return iterator\n" + " }\n" + "\n" + " iteratorValue(iterator) { _sequence.iteratorValue(iterator) }\n" + "}\n" + "\n" + "class String is Sequence {\n" + " bytes { StringByteSequence.new(this) }\n" + " codePoints { StringCodePointSequence.new(this) }\n" + "\n" + " split(delimiter) {\n" + " if (!(delimiter is String) || delimiter.isEmpty) {\n" + " Fiber.abort(\"Delimiter must be a non-empty string.\")\n" + " }\n" + "\n" + " var result = []\n" + "\n" + " var last = 0\n" + " var index = 0\n" + "\n" + " var delimSize = delimiter.byteCount_\n" + " var size = byteCount_\n" + "\n" + " while (last < size && (index = indexOf(delimiter, last)) != -1) {\n" + " result.add(this[last...index])\n" + " last = index + delimSize\n" + " }\n" + "\n" + " if (last < size) {\n" + " result.add(this[last..-1])\n" + " } else {\n" + " result.add(\"\")\n" + " }\n" + " return result\n" + " }\n" + "\n" + " replace(from, to) {\n" + " if (!(from is String) || from.isEmpty) {\n" + " Fiber.abort(\"From must be a non-empty string.\")\n" + " } else if (!(to is String)) {\n" + " Fiber.abort(\"To must be a string.\")\n" + " }\n" + "\n" + " var result = \"\"\n" + "\n" + " var last = 0\n" + " var index = 0\n" + "\n" + " var fromSize = from.byteCount_\n" + " var size = byteCount_\n" + "\n" + " while (last < size && (index = indexOf(from, last)) != -1) {\n" + " result = result + this[last...index] + to\n" + " last = index + fromSize\n" + " }\n" + "\n" + " if (last < size) result = result + this[last..-1]\n" + "\n" + " return result\n" + " }\n" + "\n" + " trim() { trim_(\"\\t\\r\\n \", true, true) }\n" + " trim(chars) { trim_(chars, true, true) }\n" + " trimEnd() { trim_(\"\\t\\r\\n \", false, true) }\n" + " trimEnd(chars) { trim_(chars, false, true) }\n" + " trimStart() { trim_(\"\\t\\r\\n \", true, false) }\n" + " trimStart(chars) { trim_(chars, true, false) }\n" + "\n" + " trim_(chars, trimStart, trimEnd) {\n" + " if (!(chars is String)) {\n" + " Fiber.abort(\"Characters must be a string.\")\n" + " }\n" + "\n" + " var codePoints = chars.codePoints.toList\n" + "\n" + " var start\n" + " if (trimStart) {\n" + " while (start = iterate(start)) {\n" + " if (!codePoints.contains(codePointAt_(start))) break\n" + " }\n" + "\n" + " if (start == false) return \"\"\n" + " } else {\n" + " start = 0\n" + " }\n" + "\n" + " var end\n" + " if (trimEnd) {\n" + " end = byteCount_ - 1\n" + " while (end >= start) {\n" + " var codePoint = codePointAt_(end)\n" + " if (codePoint != -1 && !codePoints.contains(codePoint)) break\n" + " end = end - 1\n" + " }\n" + "\n" + " if (end < start) return \"\"\n" + " } else {\n" + " end = -1\n" + " }\n" + "\n" + " return this[start..end]\n" + " }\n" + "\n" + " *(count) {\n" + " if (!(count is Num) || !count.isInteger || count < 0) {\n" + " Fiber.abort(\"Count must be a non-negative integer.\")\n" + " }\n" + "\n" + " var result = \"\"\n" + " for (i in 0...count) {\n" + " result = result + this\n" + " }\n" + " return result\n" + " }\n" + "}\n" + "\n" + "class StringByteSequence is Sequence {\n" + " construct new(string) {\n" + " _string = string\n" + " }\n" + "\n" + " [index] { _string.byteAt_(index) }\n" + " iterate(iterator) { _string.iterateByte_(iterator) }\n" + " iteratorValue(iterator) { _string.byteAt_(iterator) }\n" + "\n" + " count { _string.byteCount_ }\n" + "}\n" + "\n" + "class StringCodePointSequence is Sequence {\n" + " construct new(string) {\n" + " _string = string\n" + " }\n" + "\n" + " [index] { _string.codePointAt_(index) }\n" + " iterate(iterator) { _string.iterate(iterator) }\n" + " iteratorValue(iterator) { _string.codePointAt_(iterator) }\n" + "\n" + " count { _string.count }\n" + "}\n" + "\n" + "class List is Sequence {\n" + " addAll(other) {\n" + " for (element in other) {\n" + " add(element)\n" + " }\n" + " return other\n" + " }\n" + "\n" + " sort() { sort {|low, high| low < high } }\n" + "\n" + " sort(comparer) {\n" + " if (!(comparer is Fn)) {\n" + " Fiber.abort(\"Comparer must be a function.\")\n" + " }\n" + " quicksort_(0, count - 1, comparer)\n" + " return this\n" + " }\n" + "\n" + " quicksort_(low, high, comparer) {\n" + " if (low < high) {\n" + " var p = partition_(low, high, comparer)\n" + " quicksort_(low, p - 1, comparer)\n" + " quicksort_(p + 1, high, comparer)\n" + " }\n" + " }\n" + "\n" + " partition_(low, high, comparer) {\n" + " var p = this[high]\n" + " var i = low - 1\n" + " for (j in low..(high-1)) {\n" + " if (comparer.call(this[j], p)) { \n" + " i = i + 1\n" + " var t = this[i]\n" + " this[i] = this[j]\n" + " this[j] = t\n" + " }\n" + " }\n" + " var t = this[i+1]\n" + " this[i+1] = this[high]\n" + " this[high] = t\n" + " return i+1\n" + " }\n" + "\n" + " toString { \"[%(join(\", \"))]\" }\n" + "\n" + " +(other) {\n" + " var result = this[0..-1]\n" + " for (element in other) {\n" + " result.add(element)\n" + " }\n" + " return result\n" + " }\n" + "\n" + " *(count) {\n" + " if (!(count is Num) || !count.isInteger || count < 0) {\n" + " Fiber.abort(\"Count must be a non-negative integer.\")\n" + " }\n" + "\n" + " var result = []\n" + " for (i in 0...count) {\n" + " result.addAll(this)\n" + " }\n" + " return result\n" + " }\n" + "}\n" + "\n" + "class Map is Sequence {\n" + " keys { MapKeySequence.new(this) }\n" + " values { MapValueSequence.new(this) }\n" + "\n" + " toString {\n" + " var first = true\n" + " var result = \"{\"\n" + "\n" + " for (key in keys) {\n" + " if (!first) result = result + \", \"\n" + " first = false\n" + " result = result + \"%(key): %(this[key])\"\n" + " }\n" + "\n" + " return result + \"}\"\n" + " }\n" + "\n" + " iteratorValue(iterator) {\n" + " return MapEntry.new(\n" + " keyIteratorValue_(iterator),\n" + " valueIteratorValue_(iterator))\n" + " }\n" + "}\n" + "\n" + "class MapEntry {\n" + " construct new(key, value) {\n" + " _key = key\n" + " _value = value\n" + " }\n" + "\n" + " key { _key }\n" + " value { _value }\n" + "\n" + " toString { \"%(_key):%(_value)\" }\n" + "}\n" + "\n" + "class MapKeySequence is Sequence {\n" + " construct new(map) {\n" + " _map = map\n" + " }\n" + "\n" + " iterate(n) { _map.iterate(n) }\n" + " iteratorValue(iterator) { _map.keyIteratorValue_(iterator) }\n" + "}\n" + "\n" + "class MapValueSequence is Sequence {\n" + " construct new(map) {\n" + " _map = map\n" + " }\n" + "\n" + " iterate(n) { _map.iterate(n) }\n" + " iteratorValue(iterator) { _map.valueIteratorValue_(iterator) }\n" + "}\n" + "\n" + "class Range is Sequence {}\n" + "\n" + "class System {\n" + " static print() {\n" + " writeString_(\"\\n\")\n" + " }\n" + "\n" + " static print(obj) {\n" + " writeObject_(obj)\n" + " writeString_(\"\\n\")\n" + " return obj\n" + " }\n" + "\n" + " static printAll(sequence) {\n" + " for (object in sequence) writeObject_(object)\n" + " writeString_(\"\\n\")\n" + " }\n" + "\n" + " static write(obj) {\n" + " writeObject_(obj)\n" + " return obj\n" + " }\n" + "\n" + " static writeAll(sequence) {\n" + " for (object in sequence) writeObject_(object)\n" + " }\n" + "\n" + " static writeObject_(obj) {\n" + " var string = obj.toString\n" + " if (string is String) {\n" + " writeString_(string)\n" + " } else {\n" + " writeString_(\"[invalid toString]\")\n" + " }\n" + " }\n" + "}\n" + "\n" + "class ClassAttributes {\n" + " self { _attributes }\n" + " methods { _methods }\n" + " construct new(attributes, methods) {\n" + " _attributes = attributes\n" + " _methods = methods\n" + " }\n" + " toString { \"attributes:%(_attributes) methods:%(_methods)\" }\n" + "}\n"; +// End file "wren_core.wren.inc" + +DEF_PRIMITIVE(bool_not) { + RETURN_BOOL(!AS_BOOL(args[0])); +} + +DEF_PRIMITIVE(bool_toString) { + if (AS_BOOL(args[0])) { + RETURN_VAL(CONST_STRING(vm, "true")); + } else { + RETURN_VAL(CONST_STRING(vm, "false")); + } +} + +DEF_PRIMITIVE(class_name) { + RETURN_OBJ(AS_CLASS(args[0])->name); +} + +DEF_PRIMITIVE(class_supertype) { + ObjClass* classObj = AS_CLASS(args[0]); + + // Object has no superclass. + if (classObj->superclass == NULL) RETURN_NULL; + + RETURN_OBJ(classObj->superclass); +} + +DEF_PRIMITIVE(class_toString) { + RETURN_OBJ(AS_CLASS(args[0])->name); +} + +DEF_PRIMITIVE(class_attributes) { + RETURN_VAL(AS_CLASS(args[0])->attributes); +} + +DEF_PRIMITIVE(fiber_new) { + if (!validateFn(vm, args[1], "Argument")) return false; + + ObjClosure* closure = AS_CLOSURE(args[1]); + if (closure->fn->arity > 1) { + RETURN_ERROR("Function cannot take more than one parameter."); + } + + RETURN_OBJ(wrenNewFiber(vm, closure)); +} + +DEF_PRIMITIVE(fiber_abort) { + vm->fiber->error = args[1]; + + // If the error is explicitly null, it's not really an abort. + return IS_NULL(args[1]); +} + +// Transfer execution to [fiber] coming from the current fiber whose stack has +// [args]. +// +// [isCall] is true if [fiber] is being called and not transferred. +// +// [hasValue] is true if a value in [args] is being passed to the new fiber. +// Otherwise, `null` is implicitly being passed. +static bool runFiber(WrenVM* vm, ObjFiber* fiber, Value* args, bool isCall, bool hasValue, const char* verb) { + if (wrenHasError(fiber)) { + RETURN_ERROR_FMT("Cannot $ an aborted fiber.", verb); + } + + if (isCall) { + // You can't call a called fiber, but you can transfer directly to it, + // which is why this check is gated on `isCall`. This way, after resuming a + // suspended fiber, it will run and then return to the fiber that called it + // and so on. + if (fiber->caller != NULL) RETURN_ERROR("Fiber has already been called."); + + if (fiber->state == FIBER_ROOT) RETURN_ERROR("Cannot call root fiber."); + + // Remember who ran it. + fiber->caller = vm->fiber; + } + + if (fiber->numFrames == 0) { + RETURN_ERROR_FMT("Cannot $ a finished fiber.", verb); + } + + // When the calling fiber resumes, we'll store the result of the call in its + // stack. If the call has two arguments (the fiber and the value), we only + // need one slot for the result, so discard the other slot now. + if (hasValue) vm->fiber->stackTop--; + + if (fiber->numFrames == 1 && fiber->frames[0].ip == fiber->frames[0].closure->fn->code.data) { + // The fiber is being started for the first time. If its function takes a + // parameter, bind an argument to it. + if (fiber->frames[0].closure->fn->arity == 1) { + fiber->stackTop[0] = hasValue ? args[1] : NULL_VAL; + fiber->stackTop++; + } + } else { + // The fiber is being resumed, make yield() or transfer() return the result. + fiber->stackTop[-1] = hasValue ? args[1] : NULL_VAL; + } + + vm->fiber = fiber; + return false; +} + +DEF_PRIMITIVE(fiber_call) { + return runFiber(vm, AS_FIBER(args[0]), args, true, false, "call"); +} + +DEF_PRIMITIVE(fiber_call1) { + return runFiber(vm, AS_FIBER(args[0]), args, true, true, "call"); +} + +DEF_PRIMITIVE(fiber_current) { + RETURN_OBJ(vm->fiber); +} + +DEF_PRIMITIVE(fiber_error) { + RETURN_VAL(AS_FIBER(args[0])->error); +} + +DEF_PRIMITIVE(fiber_isDone) { + ObjFiber* runFiber = AS_FIBER(args[0]); + RETURN_BOOL(runFiber->numFrames == 0 || wrenHasError(runFiber)); +} + +DEF_PRIMITIVE(fiber_suspend) { + // Switching to a null fiber tells the interpreter to stop and exit. + vm->fiber = NULL; + vm->apiStack = NULL; + return false; +} + +DEF_PRIMITIVE(fiber_transfer) { + return runFiber(vm, AS_FIBER(args[0]), args, false, false, "transfer to"); +} + +DEF_PRIMITIVE(fiber_transfer1) { + return runFiber(vm, AS_FIBER(args[0]), args, false, true, "transfer to"); +} + +DEF_PRIMITIVE(fiber_transferError) { + runFiber(vm, AS_FIBER(args[0]), args, false, true, "transfer to"); + vm->fiber->error = args[1]; + return false; +} + +DEF_PRIMITIVE(fiber_try) { + runFiber(vm, AS_FIBER(args[0]), args, true, false, "try"); + + // If we're switching to a valid fiber to try, remember that we're trying it. + if (!wrenHasError(vm->fiber)) vm->fiber->state = FIBER_TRY; + return false; +} + +DEF_PRIMITIVE(fiber_try1) { + runFiber(vm, AS_FIBER(args[0]), args, true, true, "try"); + + // If we're switching to a valid fiber to try, remember that we're trying it. + if (!wrenHasError(vm->fiber)) vm->fiber->state = FIBER_TRY; + return false; +} + +DEF_PRIMITIVE(fiber_yield) { + ObjFiber* current = vm->fiber; + vm->fiber = current->caller; + + // Unhook this fiber from the one that called it. + current->caller = NULL; + current->state = FIBER_OTHER; + + if (vm->fiber != NULL) { + // Make the caller's run method return null. + vm->fiber->stackTop[-1] = NULL_VAL; + } + + return false; +} + +DEF_PRIMITIVE(fiber_yield1) { + ObjFiber* current = vm->fiber; + vm->fiber = current->caller; + + // Unhook this fiber from the one that called it. + current->caller = NULL; + current->state = FIBER_OTHER; + + if (vm->fiber != NULL) { + // Make the caller's run method return the argument passed to yield. + vm->fiber->stackTop[-1] = args[1]; + + // When the yielding fiber resumes, we'll store the result of the yield + // call in its stack. Since Fiber.yield(value) has two arguments (the Fiber + // class and the value) and we only need one slot for the result, discard + // the other slot now. + current->stackTop--; + } + + return false; +} + +DEF_PRIMITIVE(fn_new) { + if (!validateFn(vm, args[1], "Argument")) return false; + + // The block argument is already a function, so just return it. + RETURN_VAL(args[1]); +} + +DEF_PRIMITIVE(fn_arity) { + RETURN_NUM(AS_CLOSURE(args[0])->fn->arity); +} + +static void call_fn(WrenVM* vm, Value* args, int numArgs) { + // +1 to include the function itself. + wrenCallFunction(vm, vm->fiber, AS_CLOSURE(args[0]), numArgs + 1); +} + +#define DEF_FN_CALL(numArgs) \ + DEF_PRIMITIVE(fn_call##numArgs) { \ + call_fn(vm, args, numArgs); \ + return false; \ + } + +DEF_FN_CALL(0) +DEF_FN_CALL(1) +DEF_FN_CALL(2) +DEF_FN_CALL(3) +DEF_FN_CALL(4) +DEF_FN_CALL(5) +DEF_FN_CALL(6) +DEF_FN_CALL(7) +DEF_FN_CALL(8) +DEF_FN_CALL(9) +DEF_FN_CALL(10) +DEF_FN_CALL(11) +DEF_FN_CALL(12) +DEF_FN_CALL(13) +DEF_FN_CALL(14) +DEF_FN_CALL(15) +DEF_FN_CALL(16) + +DEF_PRIMITIVE(fn_toString) { + RETURN_VAL(CONST_STRING(vm, "")); +} + +// Creates a new list of size args[1], with all elements initialized to args[2]. +DEF_PRIMITIVE(list_filled) { + if (!validateInt(vm, args[1], "Size")) return false; + if (AS_NUM(args[1]) < 0) RETURN_ERROR("Size cannot be negative."); + + uint32_t size = (uint32_t)AS_NUM(args[1]); + ObjList* list = wrenNewList(vm, size); + + for (uint32_t i = 0; i < size; i++) { + list->elements.data[i] = args[2]; + } + + RETURN_OBJ(list); +} + +DEF_PRIMITIVE(list_new) { + RETURN_OBJ(wrenNewList(vm, 0)); +} + +DEF_PRIMITIVE(list_add) { + wrenValueBufferWrite(vm, &AS_LIST(args[0])->elements, args[1]); + RETURN_VAL(args[1]); +} + +// Adds an element to the list and then returns the list itself. This is called +// by the compiler when compiling list literals instead of using add() to +// minimize stack churn. +DEF_PRIMITIVE(list_addCore) { + wrenValueBufferWrite(vm, &AS_LIST(args[0])->elements, args[1]); + + // Return the list. + RETURN_VAL(args[0]); +} + +DEF_PRIMITIVE(list_clear) { + wrenValueBufferClear(vm, &AS_LIST(args[0])->elements); + RETURN_NULL; +} + +DEF_PRIMITIVE(list_count) { + RETURN_NUM(AS_LIST(args[0])->elements.count); +} + +DEF_PRIMITIVE(list_insert) { + ObjList* list = AS_LIST(args[0]); + + // count + 1 here so you can "insert" at the very end. + uint32_t index = validateIndex(vm, args[1], list->elements.count + 1, "Index"); + if (index == UINT32_MAX) return false; + + wrenListInsert(vm, list, args[2], index); + RETURN_VAL(args[2]); +} + +DEF_PRIMITIVE(list_iterate) { + ObjList* list = AS_LIST(args[0]); + + // If we're starting the iteration, return the first index. + if (IS_NULL(args[1])) { + if (list->elements.count == 0) RETURN_FALSE; + RETURN_NUM(0); + } + + if (!validateInt(vm, args[1], "Iterator")) return false; + + // Stop if we're out of bounds. + double index = AS_NUM(args[1]); + if (index < 0 || index >= list->elements.count - 1) RETURN_FALSE; + + // Otherwise, move to the next index. + RETURN_NUM(index + 1); +} + +DEF_PRIMITIVE(list_iteratorValue) { + ObjList* list = AS_LIST(args[0]); + uint32_t index = validateIndex(vm, args[1], list->elements.count, "Iterator"); + if (index == UINT32_MAX) return false; + + RETURN_VAL(list->elements.data[index]); +} + +DEF_PRIMITIVE(list_removeAt) { + ObjList* list = AS_LIST(args[0]); + uint32_t index = validateIndex(vm, args[1], list->elements.count, "Index"); + if (index == UINT32_MAX) return false; + + RETURN_VAL(wrenListRemoveAt(vm, list, index)); +} + +DEF_PRIMITIVE(list_removeValue) { + ObjList* list = AS_LIST(args[0]); + int index = wrenListIndexOf(vm, list, args[1]); + if (index == -1) RETURN_NULL; + RETURN_VAL(wrenListRemoveAt(vm, list, index)); +} + +DEF_PRIMITIVE(list_indexOf) { + ObjList* list = AS_LIST(args[0]); + RETURN_NUM(wrenListIndexOf(vm, list, args[1])); +} + +DEF_PRIMITIVE(list_swap) { + ObjList* list = AS_LIST(args[0]); + uint32_t indexA = validateIndex(vm, args[1], list->elements.count, "Index 0"); + if (indexA == UINT32_MAX) return false; + uint32_t indexB = validateIndex(vm, args[2], list->elements.count, "Index 1"); + if (indexB == UINT32_MAX) return false; + + Value a = list->elements.data[indexA]; + list->elements.data[indexA] = list->elements.data[indexB]; + list->elements.data[indexB] = a; + + RETURN_NULL; +} + +DEF_PRIMITIVE(list_subscript) { + ObjList* list = AS_LIST(args[0]); + + if (IS_NUM(args[1])) { + uint32_t index = validateIndex(vm, args[1], list->elements.count, "Subscript"); + if (index == UINT32_MAX) return false; + + RETURN_VAL(list->elements.data[index]); + } + + if (!IS_RANGE(args[1])) { + RETURN_ERROR("Subscript must be a number or a range."); + } + + int step; + uint32_t count = list->elements.count; + uint32_t start = calculateRange(vm, AS_RANGE(args[1]), &count, &step); + if (start == UINT32_MAX) return false; + + ObjList* result = wrenNewList(vm, count); + for (uint32_t i = 0; i < count; i++) { + result->elements.data[i] = list->elements.data[start + i * step]; + } + + RETURN_OBJ(result); +} + +DEF_PRIMITIVE(list_subscriptSetter) { + ObjList* list = AS_LIST(args[0]); + uint32_t index = validateIndex(vm, args[1], list->elements.count, "Subscript"); + if (index == UINT32_MAX) return false; + + list->elements.data[index] = args[2]; + RETURN_VAL(args[2]); +} + +DEF_PRIMITIVE(map_new) { + RETURN_OBJ(wrenNewMap(vm)); +} + +DEF_PRIMITIVE(map_subscript) { + if (!validateKey(vm, args[1])) return false; + + ObjMap* map = AS_MAP(args[0]); + Value value = wrenMapGet(map, args[1]); + if (IS_UNDEFINED(value)) RETURN_NULL; + + RETURN_VAL(value); +} + +DEF_PRIMITIVE(map_subscriptSetter) { + if (!validateKey(vm, args[1])) return false; + + wrenMapSet(vm, AS_MAP(args[0]), args[1], args[2]); + RETURN_VAL(args[2]); +} + +// Adds an entry to the map and then returns the map itself. This is called by +// the compiler when compiling map literals instead of using [_]=(_) to +// minimize stack churn. +DEF_PRIMITIVE(map_addCore) { + if (!validateKey(vm, args[1])) return false; + + wrenMapSet(vm, AS_MAP(args[0]), args[1], args[2]); + + // Return the map itself. + RETURN_VAL(args[0]); +} + +DEF_PRIMITIVE(map_clear) { + wrenMapClear(vm, AS_MAP(args[0])); + RETURN_NULL; +} + +DEF_PRIMITIVE(map_containsKey) { + if (!validateKey(vm, args[1])) return false; + + RETURN_BOOL(!IS_UNDEFINED(wrenMapGet(AS_MAP(args[0]), args[1]))); +} + +DEF_PRIMITIVE(map_count) { + RETURN_NUM(AS_MAP(args[0])->count); +} + +DEF_PRIMITIVE(map_iterate) { + ObjMap* map = AS_MAP(args[0]); + + if (map->count == 0) RETURN_FALSE; + + // If we're starting the iteration, start at the first used entry. + uint32_t index = 0; + + // Otherwise, start one past the last entry we stopped at. + if (!IS_NULL(args[1])) { + if (!validateInt(vm, args[1], "Iterator")) return false; + + if (AS_NUM(args[1]) < 0) RETURN_FALSE; + index = (uint32_t)AS_NUM(args[1]); + + if (index >= map->capacity) RETURN_FALSE; + + // Advance the iterator. + index++; + } + + // Find a used entry, if any. + for (; index < map->capacity; index++) { + if (!IS_UNDEFINED(map->entries[index].key)) RETURN_NUM(index); + } + + // If we get here, walked all of the entries. + RETURN_FALSE; +} + +DEF_PRIMITIVE(map_remove) { + if (!validateKey(vm, args[1])) return false; + + RETURN_VAL(wrenMapRemoveKey(vm, AS_MAP(args[0]), args[1])); +} + +DEF_PRIMITIVE(map_keyIteratorValue) { + ObjMap* map = AS_MAP(args[0]); + uint32_t index = validateIndex(vm, args[1], map->capacity, "Iterator"); + if (index == UINT32_MAX) return false; + + MapEntry* entry = &map->entries[index]; + if (IS_UNDEFINED(entry->key)) { + RETURN_ERROR("Invalid map iterator."); + } + + RETURN_VAL(entry->key); +} + +DEF_PRIMITIVE(map_valueIteratorValue) { + ObjMap* map = AS_MAP(args[0]); + uint32_t index = validateIndex(vm, args[1], map->capacity, "Iterator"); + if (index == UINT32_MAX) return false; + + MapEntry* entry = &map->entries[index]; + if (IS_UNDEFINED(entry->key)) { + RETURN_ERROR("Invalid map iterator."); + } + + RETURN_VAL(entry->value); +} + +DEF_PRIMITIVE(null_not) { + RETURN_VAL(TRUE_VAL); +} + +DEF_PRIMITIVE(null_toString) { + RETURN_VAL(CONST_STRING(vm, "null")); +} + +DEF_PRIMITIVE(num_fromString) { + if (!validateString(vm, args[1], "Argument")) return false; + + ObjString* string = AS_STRING(args[1]); + + // Corner case: Can't parse an empty string. + if (string->length == 0) RETURN_NULL; + + errno = 0; + char* end; + double number = strtod(string->value, &end); + + // Skip past any trailing whitespace. + while (*end != '\0' && isspace((unsigned char)*end)) end++; + + if (errno == ERANGE) RETURN_ERROR("Number literal is too large."); + + // We must have consumed the entire string. Otherwise, it contains non-number + // characters and we can't parse it. + if (end < string->value + string->length) RETURN_NULL; + + RETURN_NUM(number); +} + +// Defines a primitive on Num that calls infix [op] and returns [type]. +#define DEF_NUM_CONSTANT(name, value) \ + DEF_PRIMITIVE(num_##name) { RETURN_NUM(value); } + +DEF_NUM_CONSTANT(infinity, INFINITY) +DEF_NUM_CONSTANT(nan, WREN_DOUBLE_NAN) +DEF_NUM_CONSTANT(pi, 3.14159265358979323846264338327950288) +DEF_NUM_CONSTANT(tau, 6.28318530717958647692528676655900577) + +DEF_NUM_CONSTANT(largest, DBL_MAX) +DEF_NUM_CONSTANT(smallest, DBL_MIN) + +DEF_NUM_CONSTANT(maxSafeInteger, 9007199254740991.0) +DEF_NUM_CONSTANT(minSafeInteger, -9007199254740991.0) + +// Defines a primitive on Num that calls infix [op] and returns [type]. +#define DEF_NUM_INFIX(name, op, type) \ + DEF_PRIMITIVE(num_##name) { \ + if (!validateNum(vm, args[1], "Right operand")) return false; \ + RETURN_##type(AS_NUM(args[0]) op AS_NUM(args[1])); \ + } + +DEF_NUM_INFIX(minus, -, NUM) +DEF_NUM_INFIX(plus, +, NUM) +DEF_NUM_INFIX(multiply, *, NUM) +DEF_NUM_INFIX(divide, /, NUM) +DEF_NUM_INFIX(lt, <, BOOL) +DEF_NUM_INFIX(gt, >, BOOL) +DEF_NUM_INFIX(lte, <=, BOOL) +DEF_NUM_INFIX(gte, >=, BOOL) + +// Defines a primitive on Num that call infix bitwise [op]. +#define DEF_NUM_BITWISE(name, op) \ + DEF_PRIMITIVE(num_bitwise##name) { \ + if (!validateNum(vm, args[1], "Right operand")) return false; \ + uint32_t left = (uint32_t)AS_NUM(args[0]); \ + uint32_t right = (uint32_t)AS_NUM(args[1]); \ + RETURN_NUM(left op right); \ + } + +DEF_NUM_BITWISE(And, &) +DEF_NUM_BITWISE(Or, |) +DEF_NUM_BITWISE(Xor, ^) +DEF_NUM_BITWISE(LeftShift, <<) +DEF_NUM_BITWISE(RightShift, >>) + +// Defines a primitive method on Num that returns the result of [fn]. +#define DEF_NUM_FN(name, fn) \ + DEF_PRIMITIVE(num_##name) { RETURN_NUM(fn(AS_NUM(args[0]))); } + +DEF_NUM_FN(abs, fabs) +DEF_NUM_FN(acos, acos) +DEF_NUM_FN(asin, asin) +DEF_NUM_FN(atan, atan) +DEF_NUM_FN(cbrt, cbrt) +DEF_NUM_FN(ceil, ceil) +DEF_NUM_FN(cos, cos) +DEF_NUM_FN(floor, floor) +DEF_NUM_FN(negate, -) +DEF_NUM_FN(round, round) +DEF_NUM_FN(sin, sin) +DEF_NUM_FN(sqrt, sqrt) +DEF_NUM_FN(tan, tan) +DEF_NUM_FN(log, log) +DEF_NUM_FN(log2, log2) +DEF_NUM_FN(exp, exp) + +DEF_PRIMITIVE(num_mod) { + if (!validateNum(vm, args[1], "Right operand")) return false; + RETURN_NUM(fmod(AS_NUM(args[0]), AS_NUM(args[1]))); +} + +DEF_PRIMITIVE(num_eqeq) { + if (!IS_NUM(args[1])) RETURN_FALSE; + RETURN_BOOL(AS_NUM(args[0]) == AS_NUM(args[1])); +} + +DEF_PRIMITIVE(num_bangeq) { + if (!IS_NUM(args[1])) RETURN_TRUE; + RETURN_BOOL(AS_NUM(args[0]) != AS_NUM(args[1])); +} + +DEF_PRIMITIVE(num_bitwiseNot) { + // Bitwise operators always work on 32-bit unsigned ints. + RETURN_NUM(~(uint32_t)AS_NUM(args[0])); +} + +DEF_PRIMITIVE(num_dotDot) { + if (!validateNum(vm, args[1], "Right hand side of range")) return false; + + double from = AS_NUM(args[0]); + double to = AS_NUM(args[1]); + RETURN_VAL(wrenNewRange(vm, from, to, true)); +} + +DEF_PRIMITIVE(num_dotDotDot) { + if (!validateNum(vm, args[1], "Right hand side of range")) return false; + + double from = AS_NUM(args[0]); + double to = AS_NUM(args[1]); + RETURN_VAL(wrenNewRange(vm, from, to, false)); +} + +DEF_PRIMITIVE(num_atan2) { + if (!validateNum(vm, args[1], "x value")) return false; + + RETURN_NUM(atan2(AS_NUM(args[0]), AS_NUM(args[1]))); +} + +DEF_PRIMITIVE(num_min) { + if (!validateNum(vm, args[1], "Other value")) return false; + + double value = AS_NUM(args[0]); + double other = AS_NUM(args[1]); + RETURN_NUM(value <= other ? value : other); +} + +DEF_PRIMITIVE(num_max) { + if (!validateNum(vm, args[1], "Other value")) return false; + + double value = AS_NUM(args[0]); + double other = AS_NUM(args[1]); + RETURN_NUM(value > other ? value : other); +} + +DEF_PRIMITIVE(num_clamp) { + if (!validateNum(vm, args[1], "Min value")) return false; + if (!validateNum(vm, args[2], "Max value")) return false; + + double value = AS_NUM(args[0]); + double min = AS_NUM(args[1]); + double max = AS_NUM(args[2]); + double result = (value < min) ? min : ((value > max) ? max : value); + RETURN_NUM(result); +} + +DEF_PRIMITIVE(num_pow) { + if (!validateNum(vm, args[1], "Power value")) return false; + + RETURN_NUM(pow(AS_NUM(args[0]), AS_NUM(args[1]))); +} + +DEF_PRIMITIVE(num_fraction) { + double unused; + RETURN_NUM(modf(AS_NUM(args[0]), &unused)); +} + +DEF_PRIMITIVE(num_isInfinity) { + RETURN_BOOL(isinf(AS_NUM(args[0]))); +} + +DEF_PRIMITIVE(num_isInteger) { + double value = AS_NUM(args[0]); + if (isnan(value) || isinf(value)) RETURN_FALSE; + RETURN_BOOL(trunc(value) == value); +} + +DEF_PRIMITIVE(num_isNan) { + RETURN_BOOL(isnan(AS_NUM(args[0]))); +} + +DEF_PRIMITIVE(num_sign) { + double value = AS_NUM(args[0]); + if (value > 0) { + RETURN_NUM(1); + } else if (value < 0) { + RETURN_NUM(-1); + } else { + RETURN_NUM(0); + } +} + +DEF_PRIMITIVE(num_toString) { + RETURN_VAL(wrenNumToString(vm, AS_NUM(args[0]))); +} + +DEF_PRIMITIVE(num_truncate) { + double integer; + modf(AS_NUM(args[0]), &integer); + RETURN_NUM(integer); +} + +DEF_PRIMITIVE(object_same) { + RETURN_BOOL(wrenValuesEqual(args[1], args[2])); +} + +DEF_PRIMITIVE(object_not) { + RETURN_VAL(FALSE_VAL); +} + +DEF_PRIMITIVE(object_eqeq) { + RETURN_BOOL(wrenValuesEqual(args[0], args[1])); +} + +DEF_PRIMITIVE(object_bangeq) { + RETURN_BOOL(!wrenValuesEqual(args[0], args[1])); +} + +DEF_PRIMITIVE(object_is) { + if (!IS_CLASS(args[1])) { + RETURN_ERROR("Right operand must be a class."); + } + + ObjClass* classObj = wrenGetClass(vm, args[0]); + ObjClass* baseClassObj = AS_CLASS(args[1]); + + // Walk the superclass chain looking for the class. + do { + if (baseClassObj == classObj) RETURN_BOOL(true); + + classObj = classObj->superclass; + } while (classObj != NULL); + + RETURN_BOOL(false); +} + +DEF_PRIMITIVE(object_toString) { + Obj* obj = AS_OBJ(args[0]); + Value name = OBJ_VAL(obj->classObj->name); + RETURN_VAL(wrenStringFormat(vm, "instance of @", name)); +} + +DEF_PRIMITIVE(object_type) { + RETURN_OBJ(wrenGetClass(vm, args[0])); +} + +DEF_PRIMITIVE(range_from) { + RETURN_NUM(AS_RANGE(args[0])->from); +} + +DEF_PRIMITIVE(range_to) { + RETURN_NUM(AS_RANGE(args[0])->to); +} + +DEF_PRIMITIVE(range_min) { + ObjRange* range = AS_RANGE(args[0]); + RETURN_NUM(fmin(range->from, range->to)); +} + +DEF_PRIMITIVE(range_max) { + ObjRange* range = AS_RANGE(args[0]); + RETURN_NUM(fmax(range->from, range->to)); +} + +DEF_PRIMITIVE(range_isInclusive) { + RETURN_BOOL(AS_RANGE(args[0])->isInclusive); +} + +DEF_PRIMITIVE(range_iterate) { + ObjRange* range = AS_RANGE(args[0]); + + // Special case: empty range. + if (range->from == range->to && !range->isInclusive) RETURN_FALSE; + + // Start the iteration. + if (IS_NULL(args[1])) RETURN_NUM(range->from); + + if (!validateNum(vm, args[1], "Iterator")) return false; + + double iterator = AS_NUM(args[1]); + + // Iterate towards [to] from [from]. + if (range->from < range->to) { + iterator++; + if (iterator > range->to) RETURN_FALSE; + } else { + iterator--; + if (iterator < range->to) RETURN_FALSE; + } + + if (!range->isInclusive && iterator == range->to) RETURN_FALSE; + + RETURN_NUM(iterator); +} + +DEF_PRIMITIVE(range_iteratorValue) { + // Assume the iterator is a number so that is the value of the range. + RETURN_VAL(args[1]); +} + +DEF_PRIMITIVE(range_toString) { + ObjRange* range = AS_RANGE(args[0]); + + Value from = wrenNumToString(vm, range->from); + wrenPushRoot(vm, AS_OBJ(from)); + + Value to = wrenNumToString(vm, range->to); + wrenPushRoot(vm, AS_OBJ(to)); + + Value result = wrenStringFormat(vm, "@$@", from, range->isInclusive ? ".." : "...", to); + + wrenPopRoot(vm); + wrenPopRoot(vm); + RETURN_VAL(result); +} + +DEF_PRIMITIVE(string_fromCodePoint) { + if (!validateInt(vm, args[1], "Code point")) return false; + + int codePoint = (int)AS_NUM(args[1]); + if (codePoint < 0) { + RETURN_ERROR("Code point cannot be negative."); + } else if (codePoint > 0x10ffff) { + RETURN_ERROR("Code point cannot be greater than 0x10ffff."); + } + + RETURN_VAL(wrenStringFromCodePoint(vm, codePoint)); +} + +DEF_PRIMITIVE(string_fromByte) { + if (!validateInt(vm, args[1], "Byte")) return false; + int byte = (int)AS_NUM(args[1]); + if (byte < 0) { + RETURN_ERROR("Byte cannot be negative."); + } else if (byte > 0xff) { + RETURN_ERROR("Byte cannot be greater than 0xff."); + } + RETURN_VAL(wrenStringFromByte(vm, (uint8_t)byte)); +} + +DEF_PRIMITIVE(string_byteAt) { + ObjString* string = AS_STRING(args[0]); + + uint32_t index = validateIndex(vm, args[1], string->length, "Index"); + if (index == UINT32_MAX) return false; + + RETURN_NUM((uint8_t)string->value[index]); +} + +DEF_PRIMITIVE(string_byteCount) { + RETURN_NUM(AS_STRING(args[0])->length); +} + +DEF_PRIMITIVE(string_codePointAt) { + ObjString* string = AS_STRING(args[0]); + + uint32_t index = validateIndex(vm, args[1], string->length, "Index"); + if (index == UINT32_MAX) return false; + + // If we are in the middle of a UTF-8 sequence, indicate that. + const uint8_t* bytes = (uint8_t*)string->value; + if ((bytes[index] & 0xc0) == 0x80) RETURN_NUM(-1); + + // Decode the UTF-8 sequence. + RETURN_NUM(wrenUtf8Decode((uint8_t*)string->value + index, string->length - index)); +} + +DEF_PRIMITIVE(string_contains) { + if (!validateString(vm, args[1], "Argument")) return false; + + ObjString* string = AS_STRING(args[0]); + ObjString* search = AS_STRING(args[1]); + + RETURN_BOOL(wrenStringFind(string, search, 0) != UINT32_MAX); +} + +DEF_PRIMITIVE(string_endsWith) { + if (!validateString(vm, args[1], "Argument")) return false; + + ObjString* string = AS_STRING(args[0]); + ObjString* search = AS_STRING(args[1]); + + // Edge case: If the search string is longer then return false right away. + if (search->length > string->length) RETURN_FALSE; + + RETURN_BOOL(memcmp(string->value + string->length - search->length, search->value, search->length) == 0); +} + +DEF_PRIMITIVE(string_indexOf1) { + if (!validateString(vm, args[1], "Argument")) return false; + + ObjString* string = AS_STRING(args[0]); + ObjString* search = AS_STRING(args[1]); + + uint32_t index = wrenStringFind(string, search, 0); + RETURN_NUM(index == UINT32_MAX ? -1 : (int)index); +} + +DEF_PRIMITIVE(string_indexOf2) { + if (!validateString(vm, args[1], "Argument")) return false; + + ObjString* string = AS_STRING(args[0]); + ObjString* search = AS_STRING(args[1]); + uint32_t start = validateIndex(vm, args[2], string->length, "Start"); + if (start == UINT32_MAX) return false; + + uint32_t index = wrenStringFind(string, search, start); + RETURN_NUM(index == UINT32_MAX ? -1 : (int)index); +} + +DEF_PRIMITIVE(string_iterate) { + ObjString* string = AS_STRING(args[0]); + + // If we're starting the iteration, return the first index. + if (IS_NULL(args[1])) { + if (string->length == 0) RETURN_FALSE; + RETURN_NUM(0); + } + + if (!validateInt(vm, args[1], "Iterator")) return false; + + if (AS_NUM(args[1]) < 0) RETURN_FALSE; + uint32_t index = (uint32_t)AS_NUM(args[1]); + + // Advance to the beginning of the next UTF-8 sequence. + do { + index++; + if (index >= string->length) RETURN_FALSE; + } while ((string->value[index] & 0xc0) == 0x80); + + RETURN_NUM(index); +} + +DEF_PRIMITIVE(string_iterateByte) { + ObjString* string = AS_STRING(args[0]); + + // If we're starting the iteration, return the first index. + if (IS_NULL(args[1])) { + if (string->length == 0) RETURN_FALSE; + RETURN_NUM(0); + } + + if (!validateInt(vm, args[1], "Iterator")) return false; + + if (AS_NUM(args[1]) < 0) RETURN_FALSE; + uint32_t index = (uint32_t)AS_NUM(args[1]); + + // Advance to the next byte. + index++; + if (index >= string->length) RETURN_FALSE; + + RETURN_NUM(index); +} + +DEF_PRIMITIVE(string_iteratorValue) { + ObjString* string = AS_STRING(args[0]); + uint32_t index = validateIndex(vm, args[1], string->length, "Iterator"); + if (index == UINT32_MAX) return false; + + RETURN_VAL(wrenStringCodePointAt(vm, string, index)); +} + +DEF_PRIMITIVE(string_startsWith) { + if (!validateString(vm, args[1], "Argument")) return false; + + ObjString* string = AS_STRING(args[0]); + ObjString* search = AS_STRING(args[1]); + + // Edge case: If the search string is longer then return false right away. + if (search->length > string->length) RETURN_FALSE; + + RETURN_BOOL(memcmp(string->value, search->value, search->length) == 0); +} + +DEF_PRIMITIVE(string_plus) { + if (!validateString(vm, args[1], "Right operand")) return false; + RETURN_VAL(wrenStringFormat(vm, "@@", args[0], args[1])); +} + +DEF_PRIMITIVE(string_subscript) { + ObjString* string = AS_STRING(args[0]); + + if (IS_NUM(args[1])) { + int index = validateIndex(vm, args[1], string->length, "Subscript"); + if (index == -1) return false; + + RETURN_VAL(wrenStringCodePointAt(vm, string, index)); + } + + if (!IS_RANGE(args[1])) { + RETURN_ERROR("Subscript must be a number or a range."); + } + + int step; + uint32_t count = string->length; + int start = calculateRange(vm, AS_RANGE(args[1]), &count, &step); + if (start == -1) return false; + + RETURN_VAL(wrenNewStringFromRange(vm, string, start, count, step)); +} + +DEF_PRIMITIVE(string_toString) { + RETURN_VAL(args[0]); +} + +DEF_PRIMITIVE(system_clock) { + RETURN_NUM((double)clock() / CLOCKS_PER_SEC); +} + +DEF_PRIMITIVE(system_gc) { + wrenCollectGarbage(vm); + RETURN_NULL; +} + +DEF_PRIMITIVE(system_writeString) { + if (vm->config.writeFn != NULL) { + vm->config.writeFn(vm, AS_CSTRING(args[1])); + } + + RETURN_VAL(args[1]); +} + +// Creates either the Object or Class class in the core module with [name]. +static ObjClass* defineClass(WrenVM* vm, ObjModule* module, const char* name) { + ObjString* nameString = AS_STRING(wrenNewString(vm, name)); + wrenPushRoot(vm, (Obj*)nameString); + + ObjClass* classObj = wrenNewSingleClass(vm, 0, nameString); + + wrenDefineVariable(vm, module, name, nameString->length, OBJ_VAL(classObj), NULL); + + wrenPopRoot(vm); + return classObj; +} + +void wrenInitializeCore(WrenVM* vm) { + ObjModule* coreModule = wrenNewModule(vm, NULL); + wrenPushRoot(vm, (Obj*)coreModule); + + // The core module's key is null in the module map. + wrenMapSet(vm, vm->modules, NULL_VAL, OBJ_VAL(coreModule)); + wrenPopRoot(vm); // coreModule. + + // Define the root Object class. This has to be done a little specially + // because it has no superclass. + vm->objectClass = defineClass(vm, coreModule, "Object"); + PRIMITIVE(vm->objectClass, "!", object_not); + PRIMITIVE(vm->objectClass, "==(_)", object_eqeq); + PRIMITIVE(vm->objectClass, "!=(_)", object_bangeq); + PRIMITIVE(vm->objectClass, "is(_)", object_is); + PRIMITIVE(vm->objectClass, "toString", object_toString); + PRIMITIVE(vm->objectClass, "type", object_type); + + // Now we can define Class, which is a subclass of Object. + vm->classClass = defineClass(vm, coreModule, "Class"); + wrenBindSuperclass(vm, vm->classClass, vm->objectClass); + PRIMITIVE(vm->classClass, "name", class_name); + PRIMITIVE(vm->classClass, "supertype", class_supertype); + PRIMITIVE(vm->classClass, "toString", class_toString); + PRIMITIVE(vm->classClass, "attributes", class_attributes); + + // Finally, we can define Object's metaclass which is a subclass of Class. + ObjClass* objectMetaclass = defineClass(vm, coreModule, "Object metaclass"); + + // Wire up the metaclass relationships now that all three classes are built. + vm->objectClass->obj.classObj = objectMetaclass; + objectMetaclass->obj.classObj = vm->classClass; + vm->classClass->obj.classObj = vm->classClass; + + // Do this after wiring up the metaclasses so objectMetaclass doesn't get + // collected. + wrenBindSuperclass(vm, objectMetaclass, vm->classClass); + + PRIMITIVE(objectMetaclass, "same(_,_)", object_same); + + // The core class diagram ends up looking like this, where single lines point + // to a class's superclass, and double lines point to its metaclass: + // + // .------------------------------------. .====. + // | .---------------. | # # + // v | v | v # + // .---------. .-------------------. .-------. # + // | Object |==>| Object metaclass |==>| Class |==" + // '---------' '-------------------' '-------' + // ^ ^ ^ ^ ^ + // | .--------------' # | # + // | | # | # + // .---------. .-------------------. # | # -. + // | Base |==>| Base metaclass |======" | # | + // '---------' '-------------------' | # | + // ^ | # | + // | .------------------' # | Example classes + // | | # | + // .---------. .-------------------. # | + // | Derived |==>| Derived metaclass |==========" | + // '---------' '-------------------' -' + + // The rest of the classes can now be defined normally. + wrenInterpret(vm, NULL, coreModuleSource); + + vm->boolClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Bool")); + PRIMITIVE(vm->boolClass, "toString", bool_toString); + PRIMITIVE(vm->boolClass, "!", bool_not); + + vm->fiberClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Fiber")); + PRIMITIVE(vm->fiberClass->obj.classObj, "new(_)", fiber_new); + PRIMITIVE(vm->fiberClass->obj.classObj, "abort(_)", fiber_abort); + PRIMITIVE(vm->fiberClass->obj.classObj, "current", fiber_current); + PRIMITIVE(vm->fiberClass->obj.classObj, "suspend()", fiber_suspend); + PRIMITIVE(vm->fiberClass->obj.classObj, "yield()", fiber_yield); + PRIMITIVE(vm->fiberClass->obj.classObj, "yield(_)", fiber_yield1); + PRIMITIVE(vm->fiberClass, "call()", fiber_call); + PRIMITIVE(vm->fiberClass, "call(_)", fiber_call1); + PRIMITIVE(vm->fiberClass, "error", fiber_error); + PRIMITIVE(vm->fiberClass, "isDone", fiber_isDone); + PRIMITIVE(vm->fiberClass, "transfer()", fiber_transfer); + PRIMITIVE(vm->fiberClass, "transfer(_)", fiber_transfer1); + PRIMITIVE(vm->fiberClass, "transferError(_)", fiber_transferError); + PRIMITIVE(vm->fiberClass, "try()", fiber_try); + PRIMITIVE(vm->fiberClass, "try(_)", fiber_try1); + + vm->fnClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Fn")); + PRIMITIVE(vm->fnClass->obj.classObj, "new(_)", fn_new); + + PRIMITIVE(vm->fnClass, "arity", fn_arity); + + FUNCTION_CALL(vm->fnClass, "call()", fn_call0); + FUNCTION_CALL(vm->fnClass, "call(_)", fn_call1); + FUNCTION_CALL(vm->fnClass, "call(_,_)", fn_call2); + FUNCTION_CALL(vm->fnClass, "call(_,_,_)", fn_call3); + FUNCTION_CALL(vm->fnClass, "call(_,_,_,_)", fn_call4); + FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_)", fn_call5); + FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_)", fn_call6); + FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_)", fn_call7); + FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_)", fn_call8); + FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_)", fn_call9); + FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_)", fn_call10); + FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_)", fn_call11); + FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_)", fn_call12); + FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_,_)", fn_call13); + FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_,_,_)", fn_call14); + FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_,_,_,_)", fn_call15); + FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_)", fn_call16); + + PRIMITIVE(vm->fnClass, "toString", fn_toString); + + vm->nullClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Null")); + PRIMITIVE(vm->nullClass, "!", null_not); + PRIMITIVE(vm->nullClass, "toString", null_toString); + + vm->numClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Num")); + PRIMITIVE(vm->numClass->obj.classObj, "fromString(_)", num_fromString); + PRIMITIVE(vm->numClass->obj.classObj, "infinity", num_infinity); + PRIMITIVE(vm->numClass->obj.classObj, "nan", num_nan); + PRIMITIVE(vm->numClass->obj.classObj, "pi", num_pi); + PRIMITIVE(vm->numClass->obj.classObj, "tau", num_tau); + PRIMITIVE(vm->numClass->obj.classObj, "largest", num_largest); + PRIMITIVE(vm->numClass->obj.classObj, "smallest", num_smallest); + PRIMITIVE(vm->numClass->obj.classObj, "maxSafeInteger", num_maxSafeInteger); + PRIMITIVE(vm->numClass->obj.classObj, "minSafeInteger", num_minSafeInteger); + PRIMITIVE(vm->numClass, "-(_)", num_minus); + PRIMITIVE(vm->numClass, "+(_)", num_plus); + PRIMITIVE(vm->numClass, "*(_)", num_multiply); + PRIMITIVE(vm->numClass, "/(_)", num_divide); + PRIMITIVE(vm->numClass, "<(_)", num_lt); + PRIMITIVE(vm->numClass, ">(_)", num_gt); + PRIMITIVE(vm->numClass, "<=(_)", num_lte); + PRIMITIVE(vm->numClass, ">=(_)", num_gte); + PRIMITIVE(vm->numClass, "&(_)", num_bitwiseAnd); + PRIMITIVE(vm->numClass, "|(_)", num_bitwiseOr); + PRIMITIVE(vm->numClass, "^(_)", num_bitwiseXor); + PRIMITIVE(vm->numClass, "<<(_)", num_bitwiseLeftShift); + PRIMITIVE(vm->numClass, ">>(_)", num_bitwiseRightShift); + PRIMITIVE(vm->numClass, "abs", num_abs); + PRIMITIVE(vm->numClass, "acos", num_acos); + PRIMITIVE(vm->numClass, "asin", num_asin); + PRIMITIVE(vm->numClass, "atan", num_atan); + PRIMITIVE(vm->numClass, "cbrt", num_cbrt); + PRIMITIVE(vm->numClass, "ceil", num_ceil); + PRIMITIVE(vm->numClass, "cos", num_cos); + PRIMITIVE(vm->numClass, "floor", num_floor); + PRIMITIVE(vm->numClass, "-", num_negate); + PRIMITIVE(vm->numClass, "round", num_round); + PRIMITIVE(vm->numClass, "min(_)", num_min); + PRIMITIVE(vm->numClass, "max(_)", num_max); + PRIMITIVE(vm->numClass, "clamp(_,_)", num_clamp); + PRIMITIVE(vm->numClass, "sin", num_sin); + PRIMITIVE(vm->numClass, "sqrt", num_sqrt); + PRIMITIVE(vm->numClass, "tan", num_tan); + PRIMITIVE(vm->numClass, "log", num_log); + PRIMITIVE(vm->numClass, "log2", num_log2); + PRIMITIVE(vm->numClass, "exp", num_exp); + PRIMITIVE(vm->numClass, "%(_)", num_mod); + PRIMITIVE(vm->numClass, "~", num_bitwiseNot); + PRIMITIVE(vm->numClass, "..(_)", num_dotDot); + PRIMITIVE(vm->numClass, "...(_)", num_dotDotDot); + PRIMITIVE(vm->numClass, "atan(_)", num_atan2); + PRIMITIVE(vm->numClass, "pow(_)", num_pow); + PRIMITIVE(vm->numClass, "fraction", num_fraction); + PRIMITIVE(vm->numClass, "isInfinity", num_isInfinity); + PRIMITIVE(vm->numClass, "isInteger", num_isInteger); + PRIMITIVE(vm->numClass, "isNan", num_isNan); + PRIMITIVE(vm->numClass, "sign", num_sign); + PRIMITIVE(vm->numClass, "toString", num_toString); + PRIMITIVE(vm->numClass, "truncate", num_truncate); + + // These are defined just so that 0 and -0 are equal, which is specified by + // IEEE 754 even though they have different bit representations. + PRIMITIVE(vm->numClass, "==(_)", num_eqeq); + PRIMITIVE(vm->numClass, "!=(_)", num_bangeq); + + vm->stringClass = AS_CLASS(wrenFindVariable(vm, coreModule, "String")); + PRIMITIVE(vm->stringClass->obj.classObj, "fromCodePoint(_)", string_fromCodePoint); + PRIMITIVE(vm->stringClass->obj.classObj, "fromByte(_)", string_fromByte); + PRIMITIVE(vm->stringClass, "+(_)", string_plus); + PRIMITIVE(vm->stringClass, "[_]", string_subscript); + PRIMITIVE(vm->stringClass, "byteAt_(_)", string_byteAt); + PRIMITIVE(vm->stringClass, "byteCount_", string_byteCount); + PRIMITIVE(vm->stringClass, "codePointAt_(_)", string_codePointAt); + PRIMITIVE(vm->stringClass, "contains(_)", string_contains); + PRIMITIVE(vm->stringClass, "endsWith(_)", string_endsWith); + PRIMITIVE(vm->stringClass, "indexOf(_)", string_indexOf1); + PRIMITIVE(vm->stringClass, "indexOf(_,_)", string_indexOf2); + PRIMITIVE(vm->stringClass, "iterate(_)", string_iterate); + PRIMITIVE(vm->stringClass, "iterateByte_(_)", string_iterateByte); + PRIMITIVE(vm->stringClass, "iteratorValue(_)", string_iteratorValue); + PRIMITIVE(vm->stringClass, "startsWith(_)", string_startsWith); + PRIMITIVE(vm->stringClass, "toString", string_toString); + + vm->listClass = AS_CLASS(wrenFindVariable(vm, coreModule, "List")); + PRIMITIVE(vm->listClass->obj.classObj, "filled(_,_)", list_filled); + PRIMITIVE(vm->listClass->obj.classObj, "new()", list_new); + PRIMITIVE(vm->listClass, "[_]", list_subscript); + PRIMITIVE(vm->listClass, "[_]=(_)", list_subscriptSetter); + PRIMITIVE(vm->listClass, "add(_)", list_add); + PRIMITIVE(vm->listClass, "addCore_(_)", list_addCore); + PRIMITIVE(vm->listClass, "clear()", list_clear); + PRIMITIVE(vm->listClass, "count", list_count); + PRIMITIVE(vm->listClass, "insert(_,_)", list_insert); + PRIMITIVE(vm->listClass, "iterate(_)", list_iterate); + PRIMITIVE(vm->listClass, "iteratorValue(_)", list_iteratorValue); + PRIMITIVE(vm->listClass, "removeAt(_)", list_removeAt); + PRIMITIVE(vm->listClass, "remove(_)", list_removeValue); + PRIMITIVE(vm->listClass, "indexOf(_)", list_indexOf); + PRIMITIVE(vm->listClass, "swap(_,_)", list_swap); + + vm->mapClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Map")); + PRIMITIVE(vm->mapClass->obj.classObj, "new()", map_new); + PRIMITIVE(vm->mapClass, "[_]", map_subscript); + PRIMITIVE(vm->mapClass, "[_]=(_)", map_subscriptSetter); + PRIMITIVE(vm->mapClass, "addCore_(_,_)", map_addCore); + PRIMITIVE(vm->mapClass, "clear()", map_clear); + PRIMITIVE(vm->mapClass, "containsKey(_)", map_containsKey); + PRIMITIVE(vm->mapClass, "count", map_count); + PRIMITIVE(vm->mapClass, "remove(_)", map_remove); + PRIMITIVE(vm->mapClass, "iterate(_)", map_iterate); + PRIMITIVE(vm->mapClass, "keyIteratorValue_(_)", map_keyIteratorValue); + PRIMITIVE(vm->mapClass, "valueIteratorValue_(_)", map_valueIteratorValue); + + vm->rangeClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Range")); + PRIMITIVE(vm->rangeClass, "from", range_from); + PRIMITIVE(vm->rangeClass, "to", range_to); + PRIMITIVE(vm->rangeClass, "min", range_min); + PRIMITIVE(vm->rangeClass, "max", range_max); + PRIMITIVE(vm->rangeClass, "isInclusive", range_isInclusive); + PRIMITIVE(vm->rangeClass, "iterate(_)", range_iterate); + PRIMITIVE(vm->rangeClass, "iteratorValue(_)", range_iteratorValue); + PRIMITIVE(vm->rangeClass, "toString", range_toString); + + ObjClass* systemClass = AS_CLASS(wrenFindVariable(vm, coreModule, "System")); + PRIMITIVE(systemClass->obj.classObj, "clock", system_clock); + PRIMITIVE(systemClass->obj.classObj, "gc()", system_gc); + PRIMITIVE(systemClass->obj.classObj, "writeString_(_)", system_writeString); + + // While bootstrapping the core types and running the core module, a number + // of string objects have been created, many of which were instantiated + // before stringClass was stored in the VM. Some of them *must* be created + // first -- the ObjClass for string itself has a reference to the ObjString + // for its name. + // + // These all currently have a NULL classObj pointer, so go back and assign + // them now that the string class is known. + for (Obj* obj = vm->first; obj != NULL; obj = obj->next) { + if (obj->type == OBJ_STRING) obj->classObj = vm->stringClass; + } +} +// End file "wren_core.c" +// Begin file "wren_primitive.c" + +#include + +// Validates that [value] is an integer within `[0, count)`. Also allows +// negative indices which map backwards from the end. Returns the valid positive +// index value. If invalid, reports an error and returns `UINT32_MAX`. +static uint32_t validateIndexValue(WrenVM* vm, uint32_t count, double value, const char* argName) { + if (!validateIntValue(vm, value, argName)) return UINT32_MAX; + + // Negative indices count from the end. + if (value < 0) value = count + value; + + // Check bounds. + if (value >= 0 && value < count) return (uint32_t)value; + + vm->fiber->error = wrenStringFormat(vm, "$ out of bounds.", argName); + return UINT32_MAX; +} + +bool validateFn(WrenVM* vm, Value arg, const char* argName) { + if (IS_CLOSURE(arg)) return true; + RETURN_ERROR_FMT("$ must be a function.", argName); +} + +bool validateNum(WrenVM* vm, Value arg, const char* argName) { + if (IS_NUM(arg)) return true; + RETURN_ERROR_FMT("$ must be a number.", argName); +} + +bool validateIntValue(WrenVM* vm, double value, const char* argName) { + if (trunc(value) == value) return true; + RETURN_ERROR_FMT("$ must be an integer.", argName); +} + +bool validateInt(WrenVM* vm, Value arg, const char* argName) { + // Make sure it's a number first. + if (!validateNum(vm, arg, argName)) return false; + return validateIntValue(vm, AS_NUM(arg), argName); +} + +bool validateKey(WrenVM* vm, Value arg) { + if (wrenMapIsValidKey(arg)) return true; + + RETURN_ERROR("Key must be a value type."); +} + +uint32_t validateIndex(WrenVM* vm, Value arg, uint32_t count, const char* argName) { + if (!validateNum(vm, arg, argName)) return UINT32_MAX; + return validateIndexValue(vm, count, AS_NUM(arg), argName); +} + +bool validateString(WrenVM* vm, Value arg, const char* argName) { + if (IS_STRING(arg)) return true; + RETURN_ERROR_FMT("$ must be a string.", argName); +} + +uint32_t calculateRange(WrenVM* vm, ObjRange* range, uint32_t* length, int* step) { + *step = 0; + + // Edge case: an empty range is allowed at the end of a sequence. This way, + // list[0..-1] and list[0...list.count] can be used to copy a list even when + // empty. + if (range->from == *length && range->to == (range->isInclusive ? -1.0 : (double)*length)) { + *length = 0; + return 0; + } + + uint32_t from = validateIndexValue(vm, *length, range->from, "Range start"); + if (from == UINT32_MAX) return UINT32_MAX; + + // Bounds check the end manually to handle exclusive ranges. + double value = range->to; + if (!validateIntValue(vm, value, "Range end")) return UINT32_MAX; + + // Negative indices count from the end. + if (value < 0) value = *length + value; + + // Convert the exclusive range to an inclusive one. + if (!range->isInclusive) { + // An exclusive range with the same start and end points is empty. + if (value == from) { + *length = 0; + return from; + } + + // Shift the endpoint to make it inclusive, handling both increasing and + // decreasing ranges. + value += value >= from ? -1 : 1; + } + + // Check bounds. + if (value < 0 || value >= *length) { + vm->fiber->error = CONST_STRING(vm, "Range end out of bounds."); + return UINT32_MAX; + } + + uint32_t to = (uint32_t)value; + *length = abs((int)(from - to)) + 1; + *step = from < to ? 1 : -1; + return from; +} +// End file "wren_primitive.c" +// Begin file "wren_opt_random.c" + +#if WREN_OPT_RANDOM + +#include +#include + +// Begin file "wren_opt_random.wren.inc" +// Generated automatically from src/optional/wren_opt_random.wren. Do not edit. +static const char* randomModuleSource = + "foreign class Random {\n" + " construct new() {\n" + " seed_()\n" + " }\n" + "\n" + " construct new(seed) {\n" + " if (seed is Num) {\n" + " seed_(seed)\n" + " } else if (seed is Sequence) {\n" + " if (seed.isEmpty) Fiber.abort(\"Sequence cannot be empty.\")\n" + "\n" + " // TODO: Empty sequence.\n" + " var seeds = []\n" + " for (element in seed) {\n" + " if (!(element is Num)) Fiber.abort(\"Sequence elements must all be numbers.\")\n" + "\n" + " seeds.add(element)\n" + " if (seeds.count == 16) break\n" + " }\n" + "\n" + " // Cycle the values to fill in any missing slots.\n" + " var i = 0\n" + " while (seeds.count < 16) {\n" + " seeds.add(seeds[i])\n" + " i = i + 1\n" + " }\n" + "\n" + " seed_(\n" + " seeds[0], seeds[1], seeds[2], seeds[3],\n" + " seeds[4], seeds[5], seeds[6], seeds[7],\n" + " seeds[8], seeds[9], seeds[10], seeds[11],\n" + " seeds[12], seeds[13], seeds[14], seeds[15])\n" + " } else {\n" + " Fiber.abort(\"Seed must be a number or a sequence of numbers.\")\n" + " }\n" + " }\n" + "\n" + " foreign seed_()\n" + " foreign seed_(seed)\n" + " foreign seed_(n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12, n13, n14, n15, n16)\n" + "\n" + " foreign float()\n" + " float(end) { float() * end }\n" + " float(start, end) { float() * (end - start) + start }\n" + "\n" + " foreign int()\n" + " int(end) { (float() * end).floor }\n" + " int(start, end) { (float() * (end - start)).floor + start }\n" + "\n" + " sample(list) {\n" + " if (list.count == 0) Fiber.abort(\"Not enough elements to sample.\")\n" + " return list[int(list.count)]\n" + " }\n" + " sample(list, count) {\n" + " if (count > list.count) Fiber.abort(\"Not enough elements to sample.\")\n" + "\n" + " var result = []\n" + "\n" + " // The algorithm described in \"Programming pearls: a sample of brilliance\".\n" + " // Use a hash map for sample sizes less than 1/4 of the population size and\n" + " // an array of booleans for larger samples. This simple heuristic improves\n" + " // performance for large sample sizes as well as reduces memory usage.\n" + " if (count * 4 < list.count) {\n" + " var picked = {}\n" + " for (i in list.count - count...list.count) {\n" + " var index = int(i + 1)\n" + " if (picked.containsKey(index)) index = i\n" + " picked[index] = true\n" + " result.add(list[index])\n" + " }\n" + " } else {\n" + " var picked = List.filled(list.count, false)\n" + " for (i in list.count - count...list.count) {\n" + " var index = int(i + 1)\n" + " if (picked[index]) index = i\n" + " picked[index] = true\n" + " result.add(list[index])\n" + " }\n" + " }\n" + "\n" + " return result\n" + " }\n" + "\n" + " shuffle(list) {\n" + " if (list.isEmpty) return\n" + "\n" + " // Fisher-Yates shuffle.\n" + " for (i in 0...list.count - 1) {\n" + " var from = int(i, list.count)\n" + " var temp = list[from]\n" + " list[from] = list[i]\n" + " list[i] = temp\n" + " }\n" + " }\n" + "}\n"; +// End file "wren_opt_random.wren.inc" + +// Implements the well equidistributed long-period linear PRNG (WELL512a). +// +// https://en.wikipedia.org/wiki/Well_equidistributed_long-period_linear +typedef struct { + uint32_t state[16]; + uint32_t index; +} Well512; + +// Code from: http://www.lomont.org/Math/Papers/2008/Lomont_PRNG_2008.pdf +static uint32_t advanceState(Well512* well) { + uint32_t a, b, c, d; + a = well->state[well->index]; + c = well->state[(well->index + 13) & 15]; + b = a ^ c ^ (a << 16) ^ (c << 15); + c = well->state[(well->index + 9) & 15]; + c ^= (c >> 11); + a = well->state[well->index] = b ^ c; + d = a ^ ((a << 5) & 0xda442d24U); + + well->index = (well->index + 15) & 15; + a = well->state[well->index]; + well->state[well->index] = a ^ b ^ d ^ (a << 2) ^ (b << 18) ^ (c << 28); + return well->state[well->index]; +} + +static void randomAllocate(WrenVM* vm) { + Well512* well = (Well512*)wrenSetSlotNewForeign(vm, 0, 0, sizeof(Well512)); + well->index = 0; +} + +static void randomSeed0(WrenVM* vm) { + Well512* well = (Well512*)wrenGetSlotForeign(vm, 0); + + srand((uint32_t)time(NULL)); + for (int i = 0; i < 16; i++) { + well->state[i] = rand(); + } +} + +static void randomSeed1(WrenVM* vm) { + Well512* well = (Well512*)wrenGetSlotForeign(vm, 0); + + srand((uint32_t)wrenGetSlotDouble(vm, 1)); + for (int i = 0; i < 16; i++) { + well->state[i] = rand(); + } +} + +static void randomSeed16(WrenVM* vm) { + Well512* well = (Well512*)wrenGetSlotForeign(vm, 0); + + for (int i = 0; i < 16; i++) { + well->state[i] = (uint32_t)wrenGetSlotDouble(vm, i + 1); + } +} + +static void randomFloat(WrenVM* vm) { + Well512* well = (Well512*)wrenGetSlotForeign(vm, 0); + + // A double has 53 bits of precision in its mantissa, and we'd like to take + // full advantage of that, so we need 53 bits of random source data. + + // First, start with 32 random bits, shifted to the left 21 bits. + double result = (double)advanceState(well) * (1 << 21); + + // Then add another 21 random bits. + result += (double)(advanceState(well) & ((1 << 21) - 1)); + + // Now we have a number from 0 - (2^53). Divide be the range to get a double + // from 0 to 1.0 (half-inclusive). + result /= 9007199254740992.0; + + wrenSetSlotDouble(vm, 0, result); +} + +static void randomInt0(WrenVM* vm) { + Well512* well = (Well512*)wrenGetSlotForeign(vm, 0); + + wrenSetSlotDouble(vm, 0, (double)advanceState(well)); +} + +const char* wrenRandomSource() { + return randomModuleSource; +} + +WrenForeignClassMethods wrenRandomBindForeignClass(WrenVM* vm, const char* module, const char* className) { + ASSERT(strcmp(className, "Random") == 0, "Should be in Random class."); + WrenForeignClassMethods methods; + methods.allocate = randomAllocate; + methods.finalize = NULL; + return methods; +} + +WrenForeignMethodFn wrenRandomBindForeignMethod(WrenVM* vm, const char* className, bool isStatic, + const char* signature) { + ASSERT(strcmp(className, "Random") == 0, "Should be in Random class."); + + if (strcmp(signature, "") == 0) return randomAllocate; + if (strcmp(signature, "seed_()") == 0) return randomSeed0; + if (strcmp(signature, "seed_(_)") == 0) return randomSeed1; + + if (strcmp(signature, "seed_(_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_)") == 0) { + return randomSeed16; + } + + if (strcmp(signature, "float()") == 0) return randomFloat; + if (strcmp(signature, "int()") == 0) return randomInt0; + + ASSERT(false, "Unknown method."); + return NULL; +} + +#endif +// End file "wren_opt_random.c" +// Begin file "wren_opt_meta.c" + +#if WREN_OPT_META + +#include + +// Begin file "wren_opt_meta.wren.inc" +// Generated automatically from src/optional/wren_opt_meta.wren. Do not edit. +static const char* metaModuleSource = + "class Meta {\n" + " static getModuleVariables(module) {\n" + " if (!(module is String)) Fiber.abort(\"Module name must be a string.\")\n" + " var result = getModuleVariables_(module)\n" + " if (result != null) return result\n" + "\n" + " Fiber.abort(\"Could not find a module named '%(module)'.\")\n" + " }\n" + "\n" + " static eval(source) {\n" + " if (!(source is String)) Fiber.abort(\"Source code must be a string.\")\n" + "\n" + " var closure = compile_(source, false, false)\n" + " // TODO: Include compile errors.\n" + " if (closure == null) Fiber.abort(\"Could not compile source code.\")\n" + "\n" + " closure.call()\n" + " }\n" + "\n" + " static compileExpression(source) {\n" + " if (!(source is String)) Fiber.abort(\"Source code must be a string.\")\n" + " return compile_(source, true, true)\n" + " }\n" + "\n" + " static compile(source) {\n" + " if (!(source is String)) Fiber.abort(\"Source code must be a string.\")\n" + " return compile_(source, false, true)\n" + " }\n" + "\n" + " foreign static compile_(source, isExpression, printErrors)\n" + " foreign static getModuleVariables_(module)\n" + "}\n"; +// End file "wren_opt_meta.wren.inc" + +void metaCompile(WrenVM* vm) { + const char* source = wrenGetSlotString(vm, 1); + bool isExpression = wrenGetSlotBool(vm, 2); + bool printErrors = wrenGetSlotBool(vm, 3); + + // TODO: Allow passing in module? + // Look up the module surrounding the callsite. This is brittle. The -2 walks + // up the callstack assuming that the meta module has one level of + // indirection before hitting the user's code. Any change to meta may require + // this constant to be tweaked. + ObjFiber* currentFiber = vm->fiber; + ObjFn* fn = currentFiber->frames[currentFiber->numFrames - 2].closure->fn; + ObjString* module = fn->module->name; + + ObjClosure* closure = wrenCompileSource(vm, module->value, source, isExpression, printErrors); + + // Return the result. We can't use the public API for this since we have a + // bare ObjClosure*. + if (closure == NULL) { + vm->apiStack[0] = NULL_VAL; + } else { + vm->apiStack[0] = OBJ_VAL(closure); + } +} + +void metaGetModuleVariables(WrenVM* vm) { + wrenEnsureSlots(vm, 3); + + Value moduleValue = wrenMapGet(vm->modules, vm->apiStack[1]); + if (IS_UNDEFINED(moduleValue)) { + vm->apiStack[0] = NULL_VAL; + return; + } + + ObjModule* module = AS_MODULE(moduleValue); + ObjList* names = wrenNewList(vm, module->variableNames.count); + vm->apiStack[0] = OBJ_VAL(names); + + // Initialize the elements to null in case a collection happens when we + // allocate the strings below. + for (int i = 0; i < names->elements.count; i++) { + names->elements.data[i] = NULL_VAL; + } + + for (int i = 0; i < names->elements.count; i++) { + names->elements.data[i] = OBJ_VAL(module->variableNames.data[i]); + } +} + +const char* wrenMetaSource() { + return metaModuleSource; +} + +WrenForeignMethodFn wrenMetaBindForeignMethod(WrenVM* vm, const char* className, bool isStatic, const char* signature) { + // There is only one foreign method in the meta module. + ASSERT(strcmp(className, "Meta") == 0, "Should be in Meta class."); + ASSERT(isStatic, "Should be static."); + + if (strcmp(signature, "compile_(_,_,_)") == 0) { + return metaCompile; + } + + if (strcmp(signature, "getModuleVariables_(_)") == 0) { + return metaGetModuleVariables; + } + + ASSERT(false, "Unknown method."); + return NULL; +} + +#endif +// End file "wren_opt_meta.c" diff --git a/be/src/thirdparty/wren/wren.h b/be/src/thirdparty/wren/wren.h new file mode 100644 index 00000000000..d53ab3df5bf --- /dev/null +++ b/be/src/thirdparty/wren/wren.h @@ -0,0 +1,534 @@ +#ifndef wren_h +#define wren_h + +#include +#include +#include + +// The Wren semantic version number components. +#define WREN_VERSION_MAJOR 0 +#define WREN_VERSION_MINOR 4 +#define WREN_VERSION_PATCH 0 + +// A human-friendly string representation of the version. +#define WREN_VERSION_STRING "0.4.0" + +// A monotonically increasing numeric representation of the version number. Use +// this if you want to do range checks over versions. +#define WREN_VERSION_NUMBER (WREN_VERSION_MAJOR * 1000000 + WREN_VERSION_MINOR * 1000 + WREN_VERSION_PATCH) + +#ifndef WREN_API +#if defined(_MSC_VER) && defined(WREN_API_DLLEXPORT) +#define WREN_API __declspec(dllexport) +#else +#define WREN_API +#endif +#endif //WREN_API + +// A single virtual machine for executing Wren code. +// +// Wren has no global state, so all state stored by a running interpreter lives +// here. +typedef struct WrenVM WrenVM; + +// A handle to a Wren object. +// +// This lets code outside of the VM hold a persistent reference to an object. +// After a handle is acquired, and until it is released, this ensures the +// garbage collector will not reclaim the object it references. +typedef struct WrenHandle WrenHandle; + +// A generic allocation function that handles all explicit memory management +// used by Wren. It's used like so: +// +// - To allocate new memory, [memory] is NULL and [newSize] is the desired +// size. It should return the allocated memory or NULL on failure. +// +// - To attempt to grow an existing allocation, [memory] is the memory, and +// [newSize] is the desired size. It should return [memory] if it was able to +// grow it in place, or a new pointer if it had to move it. +// +// - To shrink memory, [memory] and [newSize] are the same as above but it will +// always return [memory]. +// +// - To free memory, [memory] will be the memory to free and [newSize] will be +// zero. It should return NULL. +typedef void* (*WrenReallocateFn)(void* memory, size_t newSize, void* userData); + +// A function callable from Wren code, but implemented in C. +typedef void (*WrenForeignMethodFn)(WrenVM* vm); + +// A finalizer function for freeing resources owned by an instance of a foreign +// class. Unlike most foreign methods, finalizers do not have access to the VM +// and should not interact with it since it's in the middle of a garbage +// collection. +typedef void (*WrenFinalizerFn)(void* data); + +// Gives the host a chance to canonicalize the imported module name, +// potentially taking into account the (previously resolved) name of the module +// that contains the import. Typically, this is used to implement relative +// imports. +typedef const char* (*WrenResolveModuleFn)(WrenVM* vm, const char* importer, const char* name); + +// Forward declare +struct WrenLoadModuleResult; + +// Called after loadModuleFn is called for module [name]. The original returned result +// is handed back to you in this callback, so that you can free memory if appropriate. +typedef void (*WrenLoadModuleCompleteFn)(WrenVM* vm, const char* name, struct WrenLoadModuleResult result); + +// The result of a loadModuleFn call. +// [source] is the source code for the module, or NULL if the module is not found. +// [onComplete] an optional callback that will be called once Wren is done with the result. +typedef struct WrenLoadModuleResult { + const char* source; + WrenLoadModuleCompleteFn onComplete; + void* userData; +} WrenLoadModuleResult; + +// Loads and returns the source code for the module [name]. +typedef WrenLoadModuleResult (*WrenLoadModuleFn)(WrenVM* vm, const char* name); + +// Returns a pointer to a foreign method on [className] in [module] with +// [signature]. +typedef WrenForeignMethodFn (*WrenBindForeignMethodFn)(WrenVM* vm, const char* module, const char* className, + bool isStatic, const char* signature); + +// Displays a string of text to the user. +typedef void (*WrenWriteFn)(WrenVM* vm, const char* text); + +typedef enum { + // A syntax or resolution error detected at compile time. + WREN_ERROR_COMPILE, + + // The error message for a runtime error. + WREN_ERROR_RUNTIME, + + // One entry of a runtime error's stack trace. + WREN_ERROR_STACK_TRACE +} WrenErrorType; + +// Reports an error to the user. +// +// An error detected during compile time is reported by calling this once with +// [type] `WREN_ERROR_COMPILE`, the resolved name of the [module] and [line] +// where the error occurs, and the compiler's error [message]. +// +// A runtime error is reported by calling this once with [type] +// `WREN_ERROR_RUNTIME`, no [module] or [line], and the runtime error's +// [message]. After that, a series of [type] `WREN_ERROR_STACK_TRACE` calls are +// made for each line in the stack trace. Each of those has the resolved +// [module] and [line] where the method or function is defined and [message] is +// the name of the method or function. +typedef void (*WrenErrorFn)(WrenVM* vm, WrenErrorType type, const char* module, int line, const char* message); + +typedef struct { + // The callback invoked when the foreign object is created. + // + // This must be provided. Inside the body of this, it must call + // [wrenSetSlotNewForeign()] exactly once. + WrenForeignMethodFn allocate; + + // The callback invoked when the garbage collector is about to collect a + // foreign object's memory. + // + // This may be `NULL` if the foreign class does not need to finalize. + WrenFinalizerFn finalize; +} WrenForeignClassMethods; + +// Returns a pair of pointers to the foreign methods used to allocate and +// finalize the data for instances of [className] in resolved [module]. +typedef WrenForeignClassMethods (*WrenBindForeignClassFn)(WrenVM* vm, const char* module, const char* className); + +typedef struct { + // The callback Wren will use to allocate, reallocate, and deallocate memory. + // + // If `NULL`, defaults to a built-in function that uses `realloc` and `free`. + WrenReallocateFn reallocateFn; + + // The callback Wren uses to resolve a module name. + // + // Some host applications may wish to support "relative" imports, where the + // meaning of an import string depends on the module that contains it. To + // support that without baking any policy into Wren itself, the VM gives the + // host a chance to resolve an import string. + // + // Before an import is loaded, it calls this, passing in the name of the + // module that contains the import and the import string. The host app can + // look at both of those and produce a new "canonical" string that uniquely + // identifies the module. This string is then used as the name of the module + // going forward. It is what is passed to [loadModuleFn], how duplicate + // imports of the same module are detected, and how the module is reported in + // stack traces. + // + // If you leave this function NULL, then the original import string is + // treated as the resolved string. + // + // If an import cannot be resolved by the embedder, it should return NULL and + // Wren will report that as a runtime error. + // + // Wren will take ownership of the string you return and free it for you, so + // it should be allocated using the same allocation function you provide + // above. + WrenResolveModuleFn resolveModuleFn; + + // The callback Wren uses to load a module. + // + // Since Wren does not talk directly to the file system, it relies on the + // embedder to physically locate and read the source code for a module. The + // first time an import appears, Wren will call this and pass in the name of + // the module being imported. The method will return a result, which contains + // the source code for that module. Memory for the source is owned by the + // host application, and can be freed using the onComplete callback. + // + // This will only be called once for any given module name. Wren caches the + // result internally so subsequent imports of the same module will use the + // previous source and not call this. + // + // If a module with the given name could not be found by the embedder, it + // should return NULL and Wren will report that as a runtime error. + WrenLoadModuleFn loadModuleFn; + + // The callback Wren uses to find a foreign method and bind it to a class. + // + // When a foreign method is declared in a class, this will be called with the + // foreign method's module, class, and signature when the class body is + // executed. It should return a pointer to the foreign function that will be + // bound to that method. + // + // If the foreign function could not be found, this should return NULL and + // Wren will report it as runtime error. + WrenBindForeignMethodFn bindForeignMethodFn; + + // The callback Wren uses to find a foreign class and get its foreign methods. + // + // When a foreign class is declared, this will be called with the class's + // module and name when the class body is executed. It should return the + // foreign functions uses to allocate and (optionally) finalize the bytes + // stored in the foreign object when an instance is created. + WrenBindForeignClassFn bindForeignClassFn; + + // The callback Wren uses to display text when `System.print()` or the other + // related functions are called. + // + // If this is `NULL`, Wren discards any printed text. + WrenWriteFn writeFn; + + // The callback Wren uses to report errors. + // + // When an error occurs, this will be called with the module name, line + // number, and an error message. If this is `NULL`, Wren doesn't report any + // errors. + WrenErrorFn errorFn; + + // The number of bytes Wren will allocate before triggering the first garbage + // collection. + // + // If zero, defaults to 10MB. + size_t initialHeapSize; + + // After a collection occurs, the threshold for the next collection is + // determined based on the number of bytes remaining in use. This allows Wren + // to shrink its memory usage automatically after reclaiming a large amount + // of memory. + // + // This can be used to ensure that the heap does not get too small, which can + // in turn lead to a large number of collections afterwards as the heap grows + // back to a usable size. + // + // If zero, defaults to 1MB. + size_t minHeapSize; + + // Wren will resize the heap automatically as the number of bytes + // remaining in use after a collection changes. This number determines the + // amount of additional memory Wren will use after a collection, as a + // percentage of the current heap size. + // + // For example, say that this is 50. After a garbage collection, when there + // are 400 bytes of memory still in use, the next collection will be triggered + // after a total of 600 bytes are allocated (including the 400 already in + // use.) + // + // Setting this to a smaller number wastes less memory, but triggers more + // frequent garbage collections. + // + // If zero, defaults to 50. + int heapGrowthPercent; + + // User-defined data associated with the VM. + void* userData; + +} WrenConfiguration; + +typedef enum { WREN_RESULT_SUCCESS, WREN_RESULT_COMPILE_ERROR, WREN_RESULT_RUNTIME_ERROR } WrenInterpretResult; + +// The type of an object stored in a slot. +// +// This is not necessarily the object's *class*, but instead its low level +// representation type. +typedef enum { + WREN_TYPE_BOOL, + WREN_TYPE_NUM, + WREN_TYPE_FOREIGN, + WREN_TYPE_LIST, + WREN_TYPE_MAP, + WREN_TYPE_NULL, + WREN_TYPE_STRING, + + // The object is of a type that isn't accessible by the C API. + WREN_TYPE_UNKNOWN +} WrenType; + +// Get the current wren version number. +// +// Can be used to range checks over versions. +WREN_API int wrenGetVersionNumber(); + +// Initializes [configuration] with all of its default values. +// +// Call this before setting the particular fields you care about. +WREN_API void wrenInitConfiguration(WrenConfiguration* configuration); + +// Creates a new Wren virtual machine using the given [configuration]. Wren +// will copy the configuration data, so the argument passed to this can be +// freed after calling this. If [configuration] is `NULL`, uses a default +// configuration. +WREN_API WrenVM* wrenNewVM(WrenConfiguration* configuration); + +// Disposes of all resources is use by [vm], which was previously created by a +// call to [wrenNewVM]. +WREN_API void wrenFreeVM(WrenVM* vm); + +// Immediately run the garbage collector to free unused memory. +WREN_API void wrenCollectGarbage(WrenVM* vm); + +// Runs [source], a string of Wren source code in a new fiber in [vm] in the +// context of resolved [module]. +WREN_API WrenInterpretResult wrenInterpret(WrenVM* vm, const char* module, const char* source); + +// Creates a handle that can be used to invoke a method with [signature] on +// using a receiver and arguments that are set up on the stack. +// +// This handle can be used repeatedly to directly invoke that method from C +// code using [wrenCall]. +// +// When you are done with this handle, it must be released using +// [wrenReleaseHandle]. +WREN_API WrenHandle* wrenMakeCallHandle(WrenVM* vm, const char* signature); + +// Calls [method], using the receiver and arguments previously set up on the +// stack. +// +// [method] must have been created by a call to [wrenMakeCallHandle]. The +// arguments to the method must be already on the stack. The receiver should be +// in slot 0 with the remaining arguments following it, in order. It is an +// error if the number of arguments provided does not match the method's +// signature. +// +// After this returns, you can access the return value from slot 0 on the stack. +WREN_API WrenInterpretResult wrenCall(WrenVM* vm, WrenHandle* method); + +// Releases the reference stored in [handle]. After calling this, [handle] can +// no longer be used. +WREN_API void wrenReleaseHandle(WrenVM* vm, WrenHandle* handle); + +// The following functions are intended to be called from foreign methods or +// finalizers. The interface Wren provides to a foreign method is like a +// register machine: you are given a numbered array of slots that values can be +// read from and written to. Values always live in a slot (unless explicitly +// captured using wrenGetSlotHandle(), which ensures the garbage collector can +// find them. +// +// When your foreign function is called, you are given one slot for the receiver +// and each argument to the method. The receiver is in slot 0 and the arguments +// are in increasingly numbered slots after that. You are free to read and +// write to those slots as you want. If you want more slots to use as scratch +// space, you can call wrenEnsureSlots() to add more. +// +// When your function returns, every slot except slot zero is discarded and the +// value in slot zero is used as the return value of the method. If you don't +// store a return value in that slot yourself, it will retain its previous +// value, the receiver. +// +// While Wren is dynamically typed, C is not. This means the C interface has to +// support the various types of primitive values a Wren variable can hold: bool, +// double, string, etc. If we supported this for every operation in the C API, +// there would be a combinatorial explosion of functions, like "get a +// double-valued element from a list", "insert a string key and double value +// into a map", etc. +// +// To avoid that, the only way to convert to and from a raw C value is by going +// into and out of a slot. All other functions work with values already in a +// slot. So, to add an element to a list, you put the list in one slot, and the +// element in another. Then there is a single API function wrenInsertInList() +// that takes the element out of that slot and puts it into the list. +// +// The goal of this API is to be easy to use while not compromising performance. +// The latter means it does not do type or bounds checking at runtime except +// using assertions which are generally removed from release builds. C is an +// unsafe language, so it's up to you to be careful to use it correctly. In +// return, you get a very fast FFI. + +// Returns the number of slots available to the current foreign method. +WREN_API int wrenGetSlotCount(WrenVM* vm); + +// Ensures that the foreign method stack has at least [numSlots] available for +// use, growing the stack if needed. +// +// Does not shrink the stack if it has more than enough slots. +// +// It is an error to call this from a finalizer. +WREN_API void wrenEnsureSlots(WrenVM* vm, int numSlots); + +// Gets the type of the object in [slot]. +WREN_API WrenType wrenGetSlotType(WrenVM* vm, int slot); + +// Reads a boolean value from [slot]. +// +// It is an error to call this if the slot does not contain a boolean value. +WREN_API bool wrenGetSlotBool(WrenVM* vm, int slot); + +// Reads a byte array from [slot]. +// +// The memory for the returned string is owned by Wren. You can inspect it +// while in your foreign method, but cannot keep a pointer to it after the +// function returns, since the garbage collector may reclaim it. +// +// Returns a pointer to the first byte of the array and fill [length] with the +// number of bytes in the array. +// +// It is an error to call this if the slot does not contain a string. +WREN_API const char* wrenGetSlotBytes(WrenVM* vm, int slot, int* length); + +// Reads a number from [slot]. +// +// It is an error to call this if the slot does not contain a number. +WREN_API double wrenGetSlotDouble(WrenVM* vm, int slot); + +// Reads a foreign object from [slot] and returns a pointer to the foreign data +// stored with it. +// +// It is an error to call this if the slot does not contain an instance of a +// foreign class. +WREN_API void* wrenGetSlotForeign(WrenVM* vm, int slot); + +// Reads a string from [slot]. +// +// The memory for the returned string is owned by Wren. You can inspect it +// while in your foreign method, but cannot keep a pointer to it after the +// function returns, since the garbage collector may reclaim it. +// +// It is an error to call this if the slot does not contain a string. +WREN_API const char* wrenGetSlotString(WrenVM* vm, int slot); + +// Creates a handle for the value stored in [slot]. +// +// This will prevent the object that is referred to from being garbage collected +// until the handle is released by calling [wrenReleaseHandle()]. +WREN_API WrenHandle* wrenGetSlotHandle(WrenVM* vm, int slot); + +// Stores the boolean [value] in [slot]. +WREN_API void wrenSetSlotBool(WrenVM* vm, int slot, bool value); + +// Stores the array [length] of [bytes] in [slot]. +// +// The bytes are copied to a new string within Wren's heap, so you can free +// memory used by them after this is called. +WREN_API void wrenSetSlotBytes(WrenVM* vm, int slot, const char* bytes, size_t length); + +// Stores the numeric [value] in [slot]. +WREN_API void wrenSetSlotDouble(WrenVM* vm, int slot, double value); + +// Creates a new instance of the foreign class stored in [classSlot] with [size] +// bytes of raw storage and places the resulting object in [slot]. +// +// This does not invoke the foreign class's constructor on the new instance. If +// you need that to happen, call the constructor from Wren, which will then +// call the allocator foreign method. In there, call this to create the object +// and then the constructor will be invoked when the allocator returns. +// +// Returns a pointer to the foreign object's data. +WREN_API void* wrenSetSlotNewForeign(WrenVM* vm, int slot, int classSlot, size_t size); + +// Stores a new empty list in [slot]. +WREN_API void wrenSetSlotNewList(WrenVM* vm, int slot); + +// Stores a new empty map in [slot]. +WREN_API void wrenSetSlotNewMap(WrenVM* vm, int slot); + +// Stores null in [slot]. +WREN_API void wrenSetSlotNull(WrenVM* vm, int slot); + +// Stores the string [text] in [slot]. +// +// The [text] is copied to a new string within Wren's heap, so you can free +// memory used by it after this is called. The length is calculated using +// [strlen()]. If the string may contain any null bytes in the middle, then you +// should use [wrenSetSlotBytes()] instead. +WREN_API void wrenSetSlotString(WrenVM* vm, int slot, const char* text); + +// Stores the value captured in [handle] in [slot]. +// +// This does not release the handle for the value. +WREN_API void wrenSetSlotHandle(WrenVM* vm, int slot, WrenHandle* handle); + +// Returns the number of elements in the list stored in [slot]. +WREN_API int wrenGetListCount(WrenVM* vm, int slot); + +// Reads element [index] from the list in [listSlot] and stores it in +// [elementSlot]. +WREN_API void wrenGetListElement(WrenVM* vm, int listSlot, int index, int elementSlot); + +// Sets the value stored at [index] in the list at [listSlot], +// to the value from [elementSlot]. +WREN_API void wrenSetListElement(WrenVM* vm, int listSlot, int index, int elementSlot); + +// Takes the value stored at [elementSlot] and inserts it into the list stored +// at [listSlot] at [index]. +// +// As in Wren, negative indexes can be used to insert from the end. To append +// an element, use `-1` for the index. +WREN_API void wrenInsertInList(WrenVM* vm, int listSlot, int index, int elementSlot); + +// Returns the number of entries in the map stored in [slot]. +WREN_API int wrenGetMapCount(WrenVM* vm, int slot); + +// Returns true if the key in [keySlot] is found in the map placed in [mapSlot]. +WREN_API bool wrenGetMapContainsKey(WrenVM* vm, int mapSlot, int keySlot); + +// Retrieves a value with the key in [keySlot] from the map in [mapSlot] and +// stores it in [valueSlot]. +WREN_API void wrenGetMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot); + +// Takes the value stored at [valueSlot] and inserts it into the map stored +// at [mapSlot] with key [keySlot]. +WREN_API void wrenSetMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot); + +// Removes a value from the map in [mapSlot], with the key from [keySlot], +// and place it in [removedValueSlot]. If not found, [removedValueSlot] is +// set to null, the same behaviour as the Wren Map API. +WREN_API void wrenRemoveMapValue(WrenVM* vm, int mapSlot, int keySlot, int removedValueSlot); + +// Looks up the top level variable with [name] in resolved [module] and stores +// it in [slot]. +WREN_API void wrenGetVariable(WrenVM* vm, const char* module, const char* name, int slot); + +// Looks up the top level variable with [name] in resolved [module], +// returns false if not found. The module must be imported at the time, +// use wrenHasModule to ensure that before calling. +WREN_API bool wrenHasVariable(WrenVM* vm, const char* module, const char* name); + +// Returns true if [module] has been imported/resolved before, false if not. +WREN_API bool wrenHasModule(WrenVM* vm, const char* module); + +// Sets the current fiber to be aborted, and uses the value in [slot] as the +// runtime error object. +WREN_API void wrenAbortFiber(WrenVM* vm, int slot); + +// Returns the user data associated with the WrenVM. +WREN_API void* wrenGetUserData(WrenVM* vm); + +// Sets user data associated with the WrenVM. +WREN_API void wrenSetUserData(WrenVM* vm, void* userData); + +#endif diff --git a/be/src/thirdparty/wren/wren.hpp b/be/src/thirdparty/wren/wren.hpp new file mode 100644 index 00000000000..9474ea0b1b8 --- /dev/null +++ b/be/src/thirdparty/wren/wren.hpp @@ -0,0 +1,11 @@ +#ifndef wren_hpp +#define wren_hpp + +// This is a convenience header for users that want to compile Wren as C and +// link to it from a C++ application. + +extern "C" { +#include "wren.h" +} + +#endif diff --git a/be/src/thirdparty/wrenbind17/wrenbind17/allocator.hpp b/be/src/thirdparty/wrenbind17/wrenbind17/allocator.hpp new file mode 100644 index 00000000000..c0145d4795c --- /dev/null +++ b/be/src/thirdparty/wrenbind17/wrenbind17/allocator.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include +#include + +#include "index.hpp" +#include "pop.hpp" +#include "push.hpp" + +/** + * @ingroup wrenbind17 + */ +namespace wrenbind17 { +void setNextError(WrenVM* vm, std::string str); + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +namespace detail { +template +struct ForeignKlassAllocator { + static T* ctor(Args&&... args) { return new T(std::forward(args)...); } + template + static T* ctorFrom(WrenVM* vm, detail::index_list) { + return ctor(PopHelper::f(vm, Is + 1)...); + } + static void allocate(WrenVM* vm) { + auto* memory = wrenSetSlotNewForeign(vm, 0, 0, sizeof(ForeignObject)); + new (memory) ForeignObject(); + auto* wrapper = reinterpret_cast*>(memory); + try { + wrapper->ptr.reset(ctorFrom(vm, detail::index_range<0, sizeof...(Args)>())); + } catch (std::exception& e) { + wrenEnsureSlots(vm, 1); + wrenSetSlotString(vm, 0, e.what()); + wrenAbortFiber(vm, 0); + } + } + static void finalize(void* memory) { + auto* wrapper = reinterpret_cast*>(memory); + wrapper->~ForeignObject(); + } +}; +} // namespace detail +#endif +} // namespace wrenbind17 diff --git a/be/src/thirdparty/wrenbind17/wrenbind17/any.hpp b/be/src/thirdparty/wrenbind17/wrenbind17/any.hpp new file mode 100644 index 00000000000..00341516ed2 --- /dev/null +++ b/be/src/thirdparty/wrenbind17/wrenbind17/any.hpp @@ -0,0 +1,234 @@ +#pragma once + +#include +#include + +#include "handle.hpp" +#include "pop.hpp" +#include "push.hpp" + +/** + * @ingroup wrenbind17 + */ +namespace wrenbind17 { +/** + * @ingroup wrenbind17 + * @brief A return value when calling a Wren function (alias Any) + * @see Any + * @details This extends the lifetime of the Wren object (handle). As long as + * this ReturnValue instance exists the Wren object will exist. + * @note This variable can safely outlive the wrenbind17::VM class. If that happens + * then functions of this class will throw wrenbind17::RuntimeError exception. This + * holder will not try to free the Wren variable if the VM has been terminated. You + * don't have to worry about the lifetime of this holder. (uses weak pointers). + */ +class ReturnValue { +public: + ReturnValue() = default; + explicit ReturnValue(const WrenType type, Handle handle) : type(type), handle(std::move(handle)) {} + ~ReturnValue() = default; + + ReturnValue(const ReturnValue& other) = delete; + ReturnValue(ReturnValue&& other) noexcept { swap(other); } + + ReturnValue& operator=(const ReturnValue& other) = delete; + ReturnValue& operator=(ReturnValue&& other) noexcept { + if (this != &other) { + swap(other); + } + return *this; + } + + void swap(ReturnValue& other) noexcept { + std::swap(type, other.type); + std::swap(handle, other.handle); + } + + /*! + * Returns the handle that this instance owns + */ + const Handle& getHandle() const { return handle; } + + /*! + * Returns the handle that this instance owns + */ + Handle& getHandle() { return handle; } + + /*! + * @brief The raw wren type held by this instance + */ + WrenType getType() const { return type; } + + /*! + * @brief Check if the value held is some specific C++ type + * @note If the value held is a Wren numeric type then checking for any + * C++ integral or floating type will return true. + */ + template + bool is() const { + if (type == WREN_TYPE_NULL) { + return false; + } + if (const auto vm = handle.getVmWeak().lock()) { + wrenEnsureSlots(vm.get(), 1); + wrenSetSlotHandle(vm.get(), 0, handle.getHandle()); + using Type = typename std::remove_reference::type>::type; + return detail::is(vm.get(), 0); + } else { + throw RuntimeError("Invalid handle"); + } + } + + bool isMap() const { return type == WREN_TYPE_MAP; } + + bool isList() const { return type == WREN_TYPE_LIST; } + + /*! + * @brief Returns the value + * @note If the value held is a Wren numeric type then getting for any + * C++ integral or floating type will result in a cast from a double to that type. + * @throws RuntimeError if this instance is invalid (constructed via the default constructor) + * @throws BadCast if the type required by specifying the template argument does not match the type held + * @tparam T the type you want to get + * @see shared() + */ + template + T as() { + if (type == WREN_TYPE_NULL) { + throw BadCast("Bad cast when getting value from Wren"); + } + if (const auto vm = handle.getVmWeak().lock()) { + wrenEnsureSlots(vm.get(), 1); + wrenSetSlotHandle(vm.get(), 0, handle.getHandle()); + return detail::PopHelper::f(vm.get(), 0); + } else { + throw RuntimeError("Invalid handle"); + } + } + + /*! + * @brief Returns the value as a shared pointer + * @note Only works for custom C++ classes (foreign classes) that have been bound to the VM. + * @throws RuntimeError if this instance is invalid (constructed via the default constructor) + * @throws BadCast if the type required by specifying the template argument does not match the type held + * @tparam T the type of the std::shared_ptr you want to get + * @see as() + */ + template + std::shared_ptr shared() { + return as>(); + } + +private: + WrenType type = WrenType::WREN_TYPE_NULL; + Handle handle; +}; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +template <> +inline bool ReturnValue::is() const { + return type == WREN_TYPE_NUM; +} + +template <> +inline bool ReturnValue::is() const { + return type == WREN_TYPE_NUM; +} + +template <> +inline bool ReturnValue::is() const { + return type == WREN_TYPE_NUM; +} + +template <> +inline bool ReturnValue::is() const { + return type == WREN_TYPE_NUM; +} + +template <> +inline bool ReturnValue::is() const { + return type == WREN_TYPE_NUM; +} + +template <> +inline bool ReturnValue::is() const { + return type == WREN_TYPE_NUM; +} + +template <> +inline bool ReturnValue::is() const { + return type == WREN_TYPE_NUM; +} + +template <> +inline bool ReturnValue::is() const { + return type == WREN_TYPE_NUM; +} + +template <> +inline bool ReturnValue::is() const { + return type == WREN_TYPE_NUM; +} + +template <> +inline bool ReturnValue::is() const { + return type == WREN_TYPE_NUM; +} + +template <> +inline bool ReturnValue::is() const { + return type == WREN_TYPE_NUM; +} + +template <> +inline bool ReturnValue::is() const { + return type == WREN_TYPE_NUM; +} + +template <> +inline bool ReturnValue::is() const { + return type == WREN_TYPE_NUM; +} + +template <> +inline bool ReturnValue::is() const { + return type == WREN_TYPE_BOOL; +} + +template <> +inline bool ReturnValue::is() const { + return type == WREN_TYPE_NULL; +} + +template <> +inline bool ReturnValue::is() const { + return type == WREN_TYPE_STRING; +} + +template <> +inline std::nullptr_t ReturnValue::as() { + if (!is()) { + throw BadCast("Return value is not a null"); + } + return nullptr; +} +#endif + +/** + * @ingroup wrenbind17 + * @see ReturnValue + * @brief An alias of ReturnValue class + */ +using Any = ReturnValue; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +template <> +inline Any detail::getSlot(WrenVM* vm, const int idx) { + const auto type = wrenGetSlotType(vm, 0); + if (type == WREN_TYPE_NULL) { + return Any(); + } + return Any(type, Handle(getSharedVm(vm), wrenGetSlotHandle(vm, idx))); +} +#endif +} // namespace wrenbind17 diff --git a/be/src/thirdparty/wrenbind17/wrenbind17/caller.hpp b/be/src/thirdparty/wrenbind17/wrenbind17/caller.hpp new file mode 100644 index 00000000000..581b6ae155e --- /dev/null +++ b/be/src/thirdparty/wrenbind17/wrenbind17/caller.hpp @@ -0,0 +1,246 @@ +#pragma once + +#include +#include + +#include "index.hpp" +#include "pop.hpp" +#include "push.hpp" + +/** + * @ingroup wrenbind17 + */ +namespace wrenbind17 { +#ifndef DOXYGEN_SHOULD_SKIP_THIS +namespace detail { +template +struct ForeginMethodReturnHelper { + static inline void push(WrenVM* vm, int index, R ret) { PushHelper::f(vm, index, ret); } +}; + +template +struct ForeginMethodReturnHelper { + static inline void push(WrenVM* vm, int index, R& ret) { PushHelper::f(vm, index, &ret); } +}; + +template +struct ForeginMethodReturnHelper { + static inline void push(WrenVM* vm, int index, const R& ret) { PushHelper::f(vm, index, &const_cast(ret)); } +}; + +template +struct ForeginMethodReturnHelper { + static inline void push(WrenVM* vm, int index, R* ret) { PushHelper::f(vm, index, ret); } +}; + +template +struct ForeginMethodReturnHelper { + static inline void push(WrenVM* vm, int index, const R* ret) { PushHelper::f(vm, index, ret); } +}; + +template +struct ForeginMethodReturnHelper { + static inline void push(WrenVM* vm, int index, R&& ret) { PushHelper::f(vm, index, std::move(ret)); } +}; + +template +struct ForeginMethodReturnHelper&> { + static inline void push(WrenVM* vm, int index, const std::shared_ptr& ret) { + PushHelper&>::f(vm, index, ret); + } +}; + +template +struct ForeginMethodReturnHelper&> { + static inline void push(WrenVM* vm, int index, std::shared_ptr& ret) { + PushHelper&>::f(vm, index, ret); + } +}; + +template +struct ForeginMethodReturnHelper&> { + static inline void push(WrenVM* vm, int index, const std::variant& ret) { + PushHelper&>::f(vm, index, ret); + } +}; + +template +struct ForeginMethodReturnHelper&> { + static inline void push(WrenVM* vm, int index, std::variant& ret) { + PushHelper&>::f(vm, index, ret); + } +}; + +template <> +inline void ForeginMethodReturnHelper::push(WrenVM* vm, int index, const std::string& ret) { + PushHelper::f(vm, index, ret); +} + +template <> +inline void ForeginMethodReturnHelper::push(WrenVM* vm, int index, std::string& ret) { + PushHelper::f(vm, index, ret); +} + +template +struct ForeignMethodCaller { + template + static void callFrom(WrenVM* vm, detail::index_list) { + auto self = PopHelper::f(vm, 0); + // R ret = (self->*Fn)(PopHelper::type>::f(vm, Is + 1)...); + // PushHelper::f(vm, 0, ret); + ForeginMethodReturnHelper::push( + vm, 0, (self->*Fn)(PopHelper::type>::f(vm, Is + 1)...)); + } + + template + static void call(WrenVM* vm) { + try { + callFrom(vm, detail::index_range<0, sizeof...(Args)>()); + } catch (...) { + exceptionHandler(vm, std::current_exception()); + } + } + + template + static void callFrom(WrenVM* vm, detail::index_list) { + auto self = PopHelper::f(vm, 0); + // R ret = (self->*Fn)(PopHelper::type>::f(vm, Is + 1)...); + // PushHelper::f(vm, 0, ret); + ForeginMethodReturnHelper::push( + vm, 0, (self->*Fn)(PopHelper::type>::f(vm, Is + 1)...)); + } + + template + static void call(WrenVM* vm) { + try { + callFrom(vm, detail::index_range<0, sizeof...(Args)>()); + } catch (...) { + exceptionHandler(vm, std::current_exception()); + } + } +}; + +template +struct ForeignMethodCaller { + template + static void callFrom(WrenVM* vm, detail::index_list) { + auto self = PopHelper::f(vm, 0); + (self->*Fn)(PopHelper::type>::f(vm, Is + 1)...); + } + + template + static void call(WrenVM* vm) { + try { + callFrom(vm, detail::index_range<0, sizeof...(Args)>()); + } catch (...) { + exceptionHandler(vm, std::current_exception()); + } + } + + template + static void callFrom(WrenVM* vm, detail::index_list) { + auto self = PopHelper::f(vm, 0); + (self->*Fn)(PopHelper::type>::f(vm, Is + 1)...); + } + + template + static void call(WrenVM* vm) { + try { + callFrom(vm, detail::index_range<0, sizeof...(Args)>()); + } catch (...) { + exceptionHandler(vm, std::current_exception()); + } + } +}; + +template +struct ForeignMethodExtCaller { + template + static void callFrom(WrenVM* vm, detail::index_list) { + auto self = PopHelper::f(vm, 0); + // R ret = (*Fn)(*self, PopHelper::type>::f(vm, Is + 1)...); + // PushHelper::f(vm, 0, ret); + ForeginMethodReturnHelper::push( + vm, 0, (*Fn)(*self, PopHelper::type>::f(vm, Is + 1)...)); + } + + template + static void call(WrenVM* vm) { + try { + callFrom(vm, detail::index_range<0, sizeof...(Args)>()); + } catch (...) { + exceptionHandler(vm, std::current_exception()); + } + } +}; + +template +struct ForeignMethodExtCaller { + template + static void callFrom(WrenVM* vm, detail::index_list) { + auto self = PopHelper::f(vm, 0); + (*Fn)(*self, PopHelper::type>::f(vm, Is + 1)...); + } + + template + static void call(WrenVM* vm) { + try { + callFrom(vm, detail::index_range<0, sizeof...(Args)>()); + } catch (...) { + exceptionHandler(vm, std::current_exception()); + } + } +}; + +template +struct ForeignFunctionCaller { + template + static void callFrom(WrenVM* vm, detail::index_list) { + // R ret = (*Fn)(PopHelper::type>::f(vm, Is + 1)...); + // PushHelper::f(vm, 0, ret); + ForeginMethodReturnHelper::push(vm, 0, + (*Fn)(PopHelper::type>::f(vm, Is + 1)...)); + } + + template + static void call(WrenVM* vm) { + try { + callFrom(vm, detail::index_range<0, sizeof...(Args)>()); + } catch (...) { + exceptionHandler(vm, std::current_exception()); + } + } +}; + +template +struct ForeignFunctionCaller { + template + static void callFrom(WrenVM* vm, detail::index_list) { + (*Fn)(PopHelper::type>::f(vm, Is + 1)...); + } + + template + static void call(WrenVM* vm) { + try { + callFrom(vm, detail::index_range<0, sizeof...(Args)>()); + } catch (...) { + exceptionHandler(vm, std::current_exception()); + } + } +}; + +template +struct ForeignPropCaller { + static void setter(WrenVM* vm) { + auto self = PopHelper::f(vm, 0); + self->*Ptr = PopHelper::f(vm, 1); + } + + static void getter(WrenVM* vm) { + auto self = PopHelper::f(vm, 0); + PushHelper::f(vm, 0, std::forward*Ptr)>(self->*Ptr)); + } +}; +} // namespace detail +#endif +} // namespace wrenbind17 diff --git a/be/src/thirdparty/wrenbind17/wrenbind17/exception.hpp b/be/src/thirdparty/wrenbind17/wrenbind17/exception.hpp new file mode 100644 index 00000000000..729c1a1a308 --- /dev/null +++ b/be/src/thirdparty/wrenbind17/wrenbind17/exception.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include + +/** + * @ingroup wrenbind17 + */ +namespace wrenbind17 { +/** + * @ingroup wrenbind17 + */ +class Exception : public std::exception { +public: + Exception() = default; + + explicit Exception(std::string msg) : msg(std::move(msg)) {} + + const char* what() const throw() override { return msg.c_str(); } + +private: + std::string msg; +}; + +/** + * @ingroup wrenbind17 + */ +class NotFound : public Exception { +public: + NotFound() : Exception("Not found") {} +}; + +/** + * @ingroup wrenbind17 + */ +class BadCast : public Exception { +public: + BadCast() : Exception("Bad cast") {} + + explicit BadCast(std::string msg) : Exception(std::move(msg)) {} +}; + +/** + * @ingroup wrenbind17 + */ +class RuntimeError : public Exception { +public: + explicit RuntimeError(std::string msg) : Exception(std::move(msg)) {} +}; + +/** + * @ingroup wrenbind17 + */ +class CompileError : public Exception { +public: + explicit CompileError(std::string msg) : Exception(std::move(msg)) {} +}; +} // namespace wrenbind17 diff --git a/be/src/thirdparty/wrenbind17/wrenbind17/foreign.hpp b/be/src/thirdparty/wrenbind17/wrenbind17/foreign.hpp new file mode 100644 index 00000000000..36f646c0a8e --- /dev/null +++ b/be/src/thirdparty/wrenbind17/wrenbind17/foreign.hpp @@ -0,0 +1,1035 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "allocator.hpp" +#include "caller.hpp" + +/** + * @ingroup wrenbind17 + */ +namespace wrenbind17 { +/** + * @ingroup wrenbind17 + */ +enum ForeignMethodOperator { + OPERATOR_GET_INDEX, + OPERATOR_SET_INDEX, + OPERATOR_SUB, + OPERATOR_ADD, + OPERATOR_MUL, + OPERATOR_DIV, + OPERATOR_NEG, + OPERATOR_MOD, + OPERATOR_EQUAL, + OPERATOR_NOT_EQUAL, + OPERATOR_GT, + OPERATOR_LT, + OPERATOR_GT_EQUAL, + OPERATOR_LT_EQUAL, + OPERATOR_SHIFT_LEFT, + OPERATOR_SHIFT_RIGHT, + OPERATOR_AND, + OPERATOR_XOR, + OPERATOR_OR +}; +/** + * @ingroup wrenbind17 + * @brief Holds information about a foreign function of a foreign class + */ +class ForeignMethod { +public: + ForeignMethod(std::string name, WrenForeignMethodFn method, const bool isStatic) + : name(std::move(name)), method(method), isStatic(isStatic) {} + + virtual ~ForeignMethod() = default; + + // If you are getting "referencing deleted function" error which is this one below, + // then you are trying to make a copy of ForeignKlass during vm.klass("Foo"); + ForeignMethod(const ForeignMethod& other) = delete; + + virtual void generate(std::ostream& os) const = 0; + + /*! + * @brief Returns the name of the method + */ + const std::string& getName() const { return name; } + + /*! + * @brief Returns the raw pointer of this method + */ + WrenForeignMethodFn getMethod() const { return method; } + + /*! + * @brief Returns true if this method is marked as static + */ + bool getStatic() const { return isStatic; } + +protected: + std::string name; + WrenForeignMethodFn method; + bool isStatic; +}; + +/** + * @ingroup wrenbind17 + * @brief Holds information about a foreign property of a foreign class + */ +class ForeignProp { +public: + ForeignProp(std::string name, WrenForeignMethodFn getter, WrenForeignMethodFn setter, const bool isStatic) + : name(std::move(name)), getter(getter), setter(setter), isStatic(isStatic) {} + + virtual ~ForeignProp() = default; + + // If you are getting "referencing deleted function" error which is this one below, + // then you are trying to make a copy of ForeignKlass during vm.klass("Foo"); + ForeignProp(const ForeignProp& other) = delete; + + void generate(std::ostream& os) const { + if (getter) os << " foreign " << name << "\n"; + if (setter) os << " foreign " << name << "=(rhs)\n"; + } + + /*! + * @brief Returns the name of this property + */ + const std::string& getName() const { return name; } + + /*! + * @brief Returns the pointer to the raw function for settings this property + */ + WrenForeignMethodFn getSetter() { return setter; } + + /*! + * @brief Returns the pointer to the raw function for getting this property + */ + WrenForeignMethodFn getGetter() { return getter; } + + /*! + * @brief Returns true if this property is static + */ + bool getStatic() const { return isStatic; } + +protected: + std::string name; + WrenForeignMethodFn getter; + WrenForeignMethodFn setter; + bool isStatic; +}; + +/** + * @ingroup wrenbind17 + * @brief A foreign class + */ +class ForeignKlass { +public: + ForeignKlass(std::string name) : name(std::move(name)), allocators{nullptr, nullptr} {} + + virtual ~ForeignKlass() = default; + + // If you are getting "referencing deleted function" error which is this one below, + // then you are trying to make a copy of this class. + ForeignKlass(const ForeignKlass& other) = delete; + + virtual void generate(std::ostream& os) const = 0; + + /*! + * @brief Looks up a foreign function that belongs to this class + */ + ForeignMethod& findFunc(const std::string& name, const bool isStatic) { + const auto it = methods.find(name); + if (it == methods.end()) throw NotFound(); + if (it->second->getStatic() != isStatic) throw NotFound(); + return *it->second; + } + + /*! + * @brief Looks up a foreign property that belongs to this class + */ + ForeignProp& findProp(const std::string& name, const bool isStatic) { + const auto it = props.find(name); + if (it == props.end()) throw NotFound(); + if (it->second->getStatic() != isStatic) throw NotFound(); + return *it->second; + } + + /*! + * @brief Finds a function based on the signature + */ + WrenForeignMethodFn findSignature(const std::string& signature, const bool isStatic) { + switch (signature[0]) { + case '[': + case '-': + case '+': + case '/': + case '*': + case '=': + case '!': + case '%': + case '<': + case '>': + case '&': + case '^': + case '|': { + // Operators + return findFunc(signature, isStatic).getMethod(); + } + default: { + if (signature.find('(') != std::string::npos) { + // Check if setter + if (signature.find("=(_)") != std::string::npos) { + return findProp(signature.substr(0, signature.find_first_of('=')), isStatic).getSetter(); + } else { + // Must be a method + return findFunc(signature, isStatic).getMethod(); + } + + } else { + return findProp(signature, isStatic).getGetter(); + } + } + } + } + + /*! + * @brief Returns the name of this foreign class + */ + const std::string& getName() const { return name; } + + /*! + * @brief Returns a struct with pointers to the allocator and deallocator + */ + WrenForeignClassMethods& getAllocators() { return allocators; } + +protected: + std::string name; + std::string ctorDef; + std::unordered_map> methods; + std::unordered_map> props; + WrenForeignClassMethods allocators; +}; + +/** + * @ingroup wrenbind17 + * @brief Type specific implementation of foreign method + */ +template +class ForeignMethodImpl : public ForeignMethod { +public: + ForeignMethodImpl(std::string name, std::string signature, WrenForeignMethodFn fn, const bool isStatic) + : ForeignMethod(std::move(name), fn, isStatic), signature(std::move(signature)) {} + ~ForeignMethodImpl() = default; + + void generate(std::ostream& os) const override { + os << " foreign " << (isStatic ? "static " : "") << signature << "\n"; + } + + static std::string generateSignature(const std::string& name) { + std::stringstream os; + constexpr auto n = sizeof...(Args); + os << name << "("; + for (size_t i = 0; i < n; i++) { + if (i == 0) + os << "arg0"; + else + os << ", arg" << i; + } + os << ")"; + return os.str(); + } + + static std::string generateSignature(const ForeignMethodOperator name) { + switch (name) { + case OPERATOR_GET_INDEX: + return "[arg]"; + case OPERATOR_SET_INDEX: + return "[arg]=(rhs)"; + case OPERATOR_ADD: + return "+(rhs)"; + case OPERATOR_SUB: + return "-(rhs)"; + case OPERATOR_DIV: + return "/(rhs)"; + case OPERATOR_MUL: + return "*(rhs)"; + case OPERATOR_MOD: + return "%(rhs)"; + case OPERATOR_EQUAL: + return "==(rhs)"; + case OPERATOR_NOT_EQUAL: + return "!=(rhs)"; + case OPERATOR_NEG: + return "-"; + case OPERATOR_GT: + return ">(rhs)"; + case OPERATOR_LT: + return "<(rhs)"; + case OPERATOR_GT_EQUAL: + return ">=(rhs)"; + case OPERATOR_LT_EQUAL: + return "<=(rhs)"; + case OPERATOR_SHIFT_LEFT: + return "<<(rhs)"; + case OPERATOR_SHIFT_RIGHT: + return ">>(rhs)"; + case OPERATOR_AND: + return "&(rhs)"; + case OPERATOR_XOR: + return "^(rhs)"; + case OPERATOR_OR: + return "|(rhs)"; + default: + throw Exception("Operator not supported"); + } + } + + static std::string generateName(const ForeignMethodOperator name) { + switch (name) { + case OPERATOR_GET_INDEX: + return "[_]"; + case OPERATOR_SET_INDEX: + return "[_]=(_)"; + case OPERATOR_ADD: + return "+(_)"; + case OPERATOR_SUB: + return "-(_)"; + case OPERATOR_DIV: + return "/(_)"; + case OPERATOR_MUL: + return "*(_)"; + case OPERATOR_MOD: + return "%(_)"; + case OPERATOR_EQUAL: + return "==(_)"; + case OPERATOR_NOT_EQUAL: + return "!=(_)"; + case OPERATOR_NEG: + return "-"; + case OPERATOR_GT: + return ">(_)"; + case OPERATOR_LT: + return "<(_)"; + case OPERATOR_GT_EQUAL: + return ">=(_)"; + case OPERATOR_LT_EQUAL: + return "<=(_)"; + case OPERATOR_SHIFT_LEFT: + return "<<(_)"; + case OPERATOR_SHIFT_RIGHT: + return ">>(_)"; + case OPERATOR_AND: + return "&(_)"; + case OPERATOR_XOR: + return "^(_)"; + case OPERATOR_OR: + return "|(_)"; + default: + throw Exception("Operator not supported"); + } + } + +private: + std::string signature; +}; + +/** + * @ingroup wrenbind17 + */ +template +class ForeignPropImpl : public ForeignProp { +public: + ForeignPropImpl(std::string name, WrenForeignMethodFn getter, WrenForeignMethodFn setter, const bool isStatic) + : ForeignProp(std::move(name), getter, setter, isStatic) {} + ~ForeignPropImpl() = default; +}; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +namespace detail { +template +inline std::string generateNameArgs() { + constexpr auto n = sizeof...(Args); + std::stringstream ss; + ss << "("; + for (size_t i = 0; i < n; i++) { + ss << "_"; + if (i != n - 1) { + ss << ","; + } + } + ss << ")"; + return ss.str(); +} + +template +struct ForeignFunctionDetails; + +template +struct ForeignFunctionDetails { + typedef ForeignMethodImpl ForeignMethodImplType; + + static std::unique_ptr make(std::string name) { + auto signature = ForeignMethodImplType::generateSignature(name); + auto p = detail::ForeignFunctionCaller::template call; + name = name + detail::generateNameArgs(); + return std::make_unique(std::move(name), std::move(signature), p, true); + } +}; + +template +struct GetPointerType { + template + static T getType(T C::*v); + + typedef decltype(getType(static_cast(nullptr))) type; +}; + +template +struct GetVarTraits; + +template +struct GetVarTraits { + using klass = C; +}; +} // namespace detail +#endif + +/** + * @ingroup wrenbind17 + * @brtief Type specific implementation of foreign class + */ +template +class ForeignKlassImpl : public ForeignKlass { +public: + ForeignKlassImpl(std::string name) : ForeignKlass(std::move(name)) { + allocators.allocate = nullptr; + allocators.finalize = nullptr; + } + + ~ForeignKlassImpl() = default; + + // If you are getting "referencing deleted function" error which is this one below, + // then you are trying to make a copy of this class. + // Make sure you are not creating a copy while registering your custom C++ class as: + // auto& cls = vm.module("mymodule").klass("Foo"); + // Notice the "auto&" + ForeignKlassImpl(const ForeignKlassImpl& other) = delete; + + /*! + * @brief Generate Wren code for this class + */ + void generate(std::ostream& os) const override { + os << "foreign class " << name << " {\n"; + if (!ctorDef.empty()) { + os << " " << ctorDef; + } + for (const auto& pair : methods) { + pair.second->generate(os); + } + for (const auto& pair : props) { + pair.second->generate(os); + } + os << "}\n\n"; + } + + /*! + * @brief Add a constructor to this class + */ + template + void ctor(const std::string& name = "new") { + allocators.allocate = &detail::ForeignKlassAllocator::allocate; + allocators.finalize = &detail::ForeignKlassAllocator::finalize; + std::stringstream ss; + ss << "construct " << name << " ("; + constexpr auto n = sizeof...(Args); + for (size_t i = 0; i < n; i++) { + if (i == 0) + ss << "arg0"; + else + ss << ", arg" << i; + } + ss << ") {}\n\n"; + ctorDef = ss.str(); + } + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + template + struct ForeignMethodDetails; + + template + struct ForeignMethodDetails { + static_assert(std::is_base_of::value, "The method belong to its own class or a base class"); + + typedef ForeignMethodImpl ForeignMethodImplType; + + static std::unique_ptr make(std::string name) { + auto signature = ForeignMethodImplType::generateSignature(name); + auto p = detail::ForeignMethodCaller::template call; + name = name + detail::generateNameArgs(); + return std::make_unique(std::move(name), std::move(signature), p, false); + } + + static std::unique_ptr make(const ForeignMethodOperator op) { + auto signature = ForeignMethodImplType::generateSignature(op); + auto name = ForeignMethodImplType::generateName(op); + auto p = detail::ForeignMethodCaller::template call; + return std::make_unique(std::move(name), std::move(signature), p, false); + } + }; + + template + struct ForeignMethodDetails { + static_assert(std::is_base_of::value, "The method belong to its own class or a base class"); + + typedef ForeignMethodImpl ForeignMethodImplType; + + static std::unique_ptr make(std::string name) { + auto signature = ForeignMethodImplType::generateSignature(name); + auto p = detail::ForeignMethodCaller::template call; + name = name + detail::generateNameArgs(); + return std::make_unique(std::move(name), std::move(signature), p, false); + } + + static std::unique_ptr make(const ForeignMethodOperator op) { + auto signature = ForeignMethodImplType::generateSignature(op); + auto name = ForeignMethodImplType::generateName(op); + auto p = detail::ForeignMethodCaller::template call; + return std::make_unique(std::move(name), std::move(signature), p, false); + } + }; + + template + struct ForeignMethodExtDetails; + + template + struct ForeignMethodExtDetails { + typedef ForeignMethodImpl ForeignMethodImplType; + + static std::unique_ptr make(std::string name) { + auto signature = ForeignMethodImplType::generateSignature(name); + auto p = detail::ForeignMethodExtCaller::template call; + name = name + detail::generateNameArgs(); + return std::make_unique(std::move(name), std::move(signature), p, false); + } + + static std::unique_ptr make(const ForeignMethodOperator op) { + auto signature = ForeignMethodImplType::generateSignature(op); + auto name = ForeignMethodImplType::generateName(op); + auto p = detail::ForeignMethodExtCaller::template call; + return std::make_unique(std::move(name), std::move(signature), p, false); + } + }; + + template + struct ForeignVarDetails { + static_assert(std::is_base_of::value, "The variable belong to its own class or a base class"); + + static std::unique_ptr make(std::string name, const bool readonly) { + auto s = readonly ? nullptr : detail::ForeignPropCaller::setter; + auto g = detail::ForeignPropCaller::getter; + return std::make_unique(std::move(name), g, s, false); + } + }; + + template + struct ForeignVarReadonlyDetails { + static_assert(std::is_base_of::value, "The variable belong to its own class or a base class"); + + static std::unique_ptr make(std::string name, const bool readonly) { + auto g = detail::ForeignPropCaller::getter; + return std::make_unique(std::move(name), g, nullptr, false); + } + }; + + template + struct ForeignSetterDetails; + + template + struct ForeignSetterDetails { + static_assert(std::is_base_of::value, "The setter must belong to its own class or a base class"); + + static WrenForeignMethodFn method() { return detail::ForeignMethodCaller::template call; } + }; + + template + struct ForeignSetterExtDetails; + + template + struct ForeignSetterExtDetails { + static WrenForeignMethodFn method() { return detail::ForeignMethodExtCaller::template call; } + }; + + template + struct ForeignGetterDetails; + + template + struct ForeignGetterDetails { + static_assert(std::is_base_of::value, "The getter must belong to its own class or a base class"); + + static WrenForeignMethodFn method() { return detail::ForeignMethodCaller::template call; } + }; + + template + struct ForeignGetterDetails { + static_assert(std::is_base_of::value, "The getter must belong to its own class or a base class"); + + static WrenForeignMethodFn method() { return detail::ForeignMethodCaller::template call; } + }; + + template + struct ForeignGetterExtDetails; + + template + struct ForeignGetterExtDetails { + static WrenForeignMethodFn method() { return detail::ForeignMethodExtCaller::template call; } + }; +#endif + + /*! + * @brief Add a member function to this class + * @details The number of arguments and what type of arguments + * this class needs is handled at compile time with metaprogramming. + * When this C++ function you are adding is called from Wren, it will check + * the types passed and will match the C++ signature. If the types do not match + * an exception is thrown that can be handled by Wren as a fiber. + * + * Example: + * + * @code + * class Foo { + * public: + * Foo(const std::string& msg) {...} + * void bar() {...} + * int baz() const {...} + * }; + * + * int main() { + * ... + * wren::VM vm; + * auto& m = vm.module("mymodule"); + * + * // Add class "Foo" + * auto& cls = m.klass("Foo"); + * + * // Define constructor (you can only specify one constructor) + * cls.ctor(); + * + * // Add some methods + * cls.func<&Foo::bar>("bar"); + * cls.func<&Foo::baz>("baz"); + * } + * @endcode + */ + template + void func(std::string name) { + auto ptr = ForeignMethodDetails::make(std::move(name)); + methods.insert(std::make_pair(ptr->getName(), std::move(ptr))); + } + + /*! + * @brief Add a member operator function to this class that exists outside of the class + * @see ForeignMethodOperator + * @details The number of arguments and what type of arguments + * this class needs is handled at compile time with metaprogramming. + * When this C++ function you are adding is called from Wren, it will check + * the types passed and will match the C++ signature. If the types do not match + * an exception is thrown that can be handled by Wren as a fiber. + * + * Example: + * + * @code + * class Foo { + * public: + * Foo(const std::string& msg) {...} + * Foo& operator+(const std::string& other) {...} + * }; + * + * int main() { + * ... + * wren::VM vm; + * auto& m = vm.module("mymodule"); + * + * // Add class "Foo" + * auto& cls = m.klass("Foo"); + * + * // Define constructor (you can only specify one constructor) + * cls.ctor(); + * + * // Add some methods + * cls.func<&Foo::operator+>(wren::ForeignMethodOperator::OPERATOR_ADD); + * } + * @endcode + */ + template + void func(const ForeignMethodOperator name) { + auto ptr = ForeignMethodDetails::make(name); + methods.insert(std::make_pair(ptr->getName(), std::move(ptr))); + } + + /*! + * @brief Add a member function via a static function + * @details The number of arguments and what type of arguments + * this class needs is handled at compile time with metaprogramming. + * When this C++ function you are adding is called from Wren, it will check + * the types passed and will match the C++ signature. If the types do not match + * an exception is thrown that can be handled by Wren as a fiber. + * + * This function does not accept class methods, but instead it uses regular functions + * that have first parameter as "this" which is a reference to the class you are + * adding. + * + * Example: + * + * @code + * class Foo { + * public: + * Foo(const std::string& msg) {...} + * + * std::string message; + * }; + * + * static std::string fooBaz(Foo& self, int a, int b) { + * return self.message; + * } + * + * int main() { + * ... + * wren::VM vm; + * auto& m = vm.module("mymodule"); + * + * // Add class "Foo" + * auto& cls = m.klass("Foo"); + * + * // Define constructor (you can only specify one constructor) + * cls.ctor(); + * + * // Add some methods + * cls.funcExt<&fooBaz>("bar"); + * } + * @endcode + */ + template + void funcExt(std::string name) { + auto ptr = ForeignMethodExtDetails::make(std::move(name)); + methods.insert(std::make_pair(ptr->getName(), std::move(ptr))); + } + + /*! + * @brief Add a member operator via a static function that exists outside of the class + * @see ForeignMethodOperator + * @details Same as funcExt but instead it can accept an operator enumeration. + */ + template + void funcExt(const ForeignMethodOperator name) { + auto ptr = ForeignMethodExtDetails::make(name); + methods.insert(std::make_pair(ptr->getName(), std::move(ptr))); + } + + /*! + * @brief Add a static function to this class + * @see func + * @details The number of arguments and what type of arguments + * this class needs is handled at compile time with metaprogramming. + * When this C++ function you are adding is called from Wren, it will check + * the types passed and will match the C++ signature. If the types do not match + * an exception is thrown that can be handled by Wren as a fiber. + * + * This only works if the function you are adding is static. + * + * Example: + * + * @code + * class Foo { + * public: + * Foo(const std::string& msg) {...} + * static void bar() {...} + * static int baz() const {...} + * }; + * + * int main() { + * ... + * wren::VM vm; + * auto& m = vm.module("mymodule"); + * + * // Add class "Foo" + * auto& cls = m.klass("Foo"); + * + * // Define constructor (you can only specify one constructor) + * cls.ctor(); + * + * // Add some methods + * cls.funcStatic<&Foo::bar>("bar"); + * cls.funcStatic<&Foo::baz>("baz"); + * } + * @endcode + */ + template + void funcStatic(std::string name) { + auto ptr = detail::ForeignFunctionDetails::make(std::move(name)); + methods.insert(std::make_pair(ptr->getName(), std::move(ptr))); + } + + /*! + * @brief Add a static function to this class that exists outside of the class + * @see funcStatic + * @details The number of arguments and what type of arguments + * this class needs is handled at compile time with metaprogramming. + * When this C++ function you are adding is called from Wren, it will check + * the types passed and will match the C++ signature. If the types do not match + * an exception is thrown that can be handled by Wren as a fiber. + * + * This only works if the function you are adding is static. + * + * Example: + * + * @code + * class Foo { + * public: + * Foo(const std::string& msg) {...} + * }; + * + * static void fooBar() { ... } + * static void fooBaz() { ... } + * + * int main() { + * ... + * wren::VM vm; + * auto& m = vm.module("mymodule"); + * + * // Add class "Foo" + * auto& cls = m.klass("Foo"); + * + * // Define constructor (you can only specify one constructor) + * cls.ctor(); + * + * // Add some methods + * cls.funcStatic<&fooBar>("bar"); + * cls.funcStatic<&fooBaz>("baz"); + * } + * @endcode + */ + template + void funcStaticExt(std::string name) { + // This is exactly the same as funcStatic because there is + // no difference for "static void Foo::foo(){}" and "void foo(){}"! + auto ptr = detail::ForeignFunctionDetails::make(std::move(name)); + methods.insert(std::make_pair(ptr->getName(), std::move(ptr))); + } + + /*! + * @brief Add a read-write variable to this class + * @details Example: + * + * @code + * class Foo { + * public: + * Foo(const std::string& msg) {...} + * + * std::string msg; + * }; + * + * int main() { + * ... + * wren::VM vm; + * auto& m = vm.module("mymodule"); + * + * // Add class "Foo" + * auto& cls = m.klass("Foo"); + * + * // Define constructor (you can only specify one constructor) + * cls.ctor(); + * + * // Add class variable as a read write Wren class property + * cls.var<&Foo::msg>("msg"); + * } + * @endcode + */ + template + void var(std::string name) { + using R = typename detail::GetPointerType::type; + using C = typename detail::GetVarTraits::klass; + auto ptr = ForeignVarDetails::make(std::move(name), false); + props.insert(std::make_pair(ptr->getName(), std::move(ptr))); + } + + /*! + * @brief Add a read-only variable to this class + * @details This is exactly the same as var() but the variable + * can be only read from Wren. It cannot be reassigned. + */ + template + void varReadonly(std::string name) { + using R = typename detail::GetPointerType::type; + using C = typename detail::GetVarTraits::klass; + auto ptr = ForeignVarReadonlyDetails::make(std::move(name), true); + props.insert(std::make_pair(ptr->getName(), std::move(ptr))); + } + + /*! + * @brief Add a read-write property to this class via a getter and a setter + * @details This essentially creates the same thing as var() + * but instead of using pointer to the class field, it uses getter and setter functions. + * @code + * class Foo { + * public: + * Foo(const std::string& msg) {...} + * void setMsg(const std::string& msg) {...} + * const std::string& getMsg() const {...} + * private: + * std::string msg; + * }; + * + * int main() { + * ... + * wren::VM vm; + * auto& m = vm.module("mymodule"); + * + * // Add class "Foo" + * auto& cls = m.klass("Foo"); + * + * // Define constructor (you can only specify one constructor) + * cls.ctor(); + * + * // Add class variable as a read write Wren class property + * cls.prop<&Foo::getMsg, &Foo::setMsg>("msg"); + * } + * @endcode + */ + template + void prop(std::string name) { + auto g = ForeignGetterDetails::method(); + auto s = ForeignSetterDetails::method(); + auto ptr = std::make_unique(std::move(name), g, s, false); + props.insert(std::make_pair(ptr->getName(), std::move(ptr))); + } + + /*! + * @brief Add a read-onlu property to this class via a getter + * @details This essentially creates the same thing as varReadonly() + * but instead of using pointer to the class field, it uses a getter. + * This property will be read only and cannot be reassigned from Wren. + * @code + * class Foo { + * public: + * Foo(const std::string& msg) {...} + * void setMsg(const std::string& msg) {...} + * const std::string& getMsg() const {...} + * private: + * std::string msg; + * }; + * + * int main() { + * ... + * wren::VM vm; + * auto& m = vm.module("mymodule"); + * + * // Add class "Foo" + * auto& cls = m.klass("Foo"); + * + * // Define constructor (you can only specify one constructor) + * cls.ctor(); + * + * // Add class variable as a read only Wren class property + * cls.propReadonly<&Foo::getMsg>("msg"); + * } + * @endcode + */ + template + void propReadonly(std::string name) { + auto g = ForeignGetterDetails::method(); + auto ptr = std::make_unique(std::move(name), g, nullptr, false); + props.insert(std::make_pair(ptr->getName(), std::move(ptr))); + } + + /*! + * @brief Add a read-write property to this class via a getter and a setter + * @see prop + * @details This essentially creates the same thing as var() + * but instead of using pointer to the class field, it uses a static getter and setter. + * The setter and getter do not have to belong to the class itself, but must be + * static functions, and must accept the class as a reference as a first parameter. + * @code + * class Foo { + * public: + * Foo(const std::string& msg) {...} + * std::string msg; + * }; + * + * static void fooSetMsg(Foo& self, const std::string& msg) { + * self.msg = msg; + * } + * + * static const std::string& msg fooGetMsg(Foo& self) { + * return self.msg; + * } + * + * int main() { + * ... + * wren::VM vm; + * auto& m = vm.module("mymodule"); + * + * // Add class "Foo" + * auto& cls = m.klass("Foo"); + * + * // Define constructor (you can only specify one constructor) + * cls.ctor(); + * + * // Add class variable as a read write Wren class property + * cls.propExt<&fooGetMsg, &fooSetMsg>("msg"); + * } + * @endcode + */ + template + void propExt(std::string name) { + auto g = ForeignGetterExtDetails::method(); + auto s = ForeignSetterExtDetails::method(); + auto ptr = std::make_unique(std::move(name), g, s, false); + props.insert(std::make_pair(ptr->getName(), std::move(ptr))); + } + + /*! + * @brief Add a read-only property to this class via a getter + * @see propReadonly + * @details This essentially creates the same thing as varReadonly() + * but instead of using pointer to the class field, it uses a static getter. + * The setter and does not have to belong to the class itself, but must be + * static function, and must accept the class as a reference as a first parameter. + * @code + * class Foo { + * public: + * Foo(const std::string& msg) {...} + * std::string msg; + * }; + * + * static const std::string& msg fooGetMsg(Foo& self) { + * return self.msg; + * } + * + * int main() { + * ... + * wren::VM vm; + * auto& m = vm.module("mymodule"); + * + * // Add class "Foo" + * auto& cls = m.klass("Foo"); + * + * // Define constructor (you can only specify one constructor) + * cls.ctor(); + * + * // Add class variable as a read only Wren class property + * cls.propReadonlyExt<&fooGetMsg>("msg"); + * } + * @endcode + */ + template + void propReadonlyExt(std::string name) { + auto g = ForeignGetterExtDetails::method(); + auto ptr = std::make_unique(std::move(name), g, nullptr, false); + props.insert(std::make_pair(ptr->getName(), std::move(ptr))); + } +}; +} // namespace wrenbind17 diff --git a/be/src/thirdparty/wrenbind17/wrenbind17/handle.hpp b/be/src/thirdparty/wrenbind17/wrenbind17/handle.hpp new file mode 100644 index 00000000000..3f27a0529cb --- /dev/null +++ b/be/src/thirdparty/wrenbind17/wrenbind17/handle.hpp @@ -0,0 +1,64 @@ +#pragma once + +#include +#include + +#include "exception.hpp" + +/** + * @ingroup wrenbind17 + */ +namespace wrenbind17 { +std::shared_ptr getSharedVm(WrenVM* vm); + +/** + * @ingroup wrenbind17 + * @brief Holds a reference to some Wren type + * @details This is used by Map, Method, and Variable classes. + */ +class Handle { +public: + Handle() : handle(nullptr) {} + Handle(const std::shared_ptr vm, WrenHandle* handle) : vm(vm), handle(handle) {} + ~Handle() { reset(); } + Handle(const Handle& other) = delete; + Handle(Handle&& other) noexcept : handle(nullptr) { swap(other); } + Handle& operator=(const Handle& other) = delete; + Handle& operator=(Handle&& other) noexcept { + if (this != &other) { + swap(other); + } + return *this; + } + void swap(Handle& other) noexcept { + std::swap(vm, other.vm); + std::swap(handle, other.handle); + } + + WrenHandle* getHandle() const { return handle; } + + WrenVM* getVm() const { + if (const auto ptr = vm.lock()) { + return ptr.get(); + } else { + throw RuntimeError("Invalid handle"); + } + } + + const std::weak_ptr& getVmWeak() const { return vm; } + + void reset() { + if (!vm.expired() && handle) { + wrenReleaseHandle(vm.lock().get(), handle); + vm.reset(); + handle = nullptr; + } + } + + operator bool() const { return !vm.expired() && handle; } + +private: + std::weak_ptr vm; + WrenHandle* handle; +}; +} // namespace wrenbind17 diff --git a/be/src/thirdparty/wrenbind17/wrenbind17/index.hpp b/be/src/thirdparty/wrenbind17/wrenbind17/index.hpp new file mode 100644 index 00000000000..da4f4f55649 --- /dev/null +++ b/be/src/thirdparty/wrenbind17/wrenbind17/index.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include + +/** + * @ingroup wrenbind17 + */ +namespace wrenbind17 { +#ifndef DOXYGEN_SHOULD_SKIP_THIS +namespace detail { +template +struct index_list {}; + +// Declare primary template for index range builder +template +struct range_builder; + +// Base step +template +struct range_builder { + typedef index_list type; +}; + +// Induction step +template +struct range_builder : public range_builder {}; + +// Meta-function that returns a [MIN, MAX) index range +template +using index_range = typename detail::range_builder::type; +} // namespace detail +#endif +} // namespace wrenbind17 diff --git a/be/src/thirdparty/wrenbind17/wrenbind17/map.hpp b/be/src/thirdparty/wrenbind17/wrenbind17/map.hpp new file mode 100644 index 00000000000..2f65dcb2f67 --- /dev/null +++ b/be/src/thirdparty/wrenbind17/wrenbind17/map.hpp @@ -0,0 +1,150 @@ +#pragma once + +#include "method.hpp" +#include "pop.hpp" +#include "push.hpp" + +/** + * @ingroup wrenbind17 + */ +namespace wrenbind17 { +/** + * @ingroup wrenbind17 + * @brief Holds native Wren map + */ +class Map { +public: + Map() {} + Map(const std::shared_ptr& handle) : handle(handle) {} + ~Map() { reset(); } + + Handle& getHandle() { return *handle; } + + const Handle& getHandle() const { return *handle; } + + operator bool() const { return handle.operator bool(); } + + void reset() { handle.reset(); } + + /*! + * @brief Checks if a key exists in this map + * @throws RuntimeError if this is an invalid map or the Wren VM has terminated + * @warning If using strings, make sure that you use std::string because + * raw C-strings are not allowed. + * @note This function accepts any type you want. Integers, strings, booleans, + * does not matter as long as Wren map supports that key type. + */ + template + bool contains(const Key& key) const { + if (const auto ptr = handle->getVmWeak().lock()) { + wrenEnsureSlots(ptr.get(), 2); + wrenSetSlotHandle(ptr.get(), 0, handle->getHandle()); + detail::PushHelper::f(ptr.get(), 1, key); + return wrenGetMapContainsKey(ptr.get(), 0, 1); + } else { + throw RuntimeError("Invalid handle"); + } + } + + /*! + * @brief Returns a value specified by T from the map by a key + * @throws NotFound if the key does not exist in the map + * @throws RuntimeError if this is an invalid map or the Wren VM has terminated + * @warning If using strings, make sure that you use std::string because + * raw C-strings are not allowed. + * @note This function accepts any type you want. Integers, strings, booleans, + * does not matter as long as Wren map supports that key type. + */ + template + T get(const Key& key) const { + if (const auto ptr = handle->getVmWeak().lock()) { + wrenEnsureSlots(ptr.get(), 3); + wrenSetSlotHandle(ptr.get(), 0, handle->getHandle()); + detail::PushHelper::f(ptr.get(), 1, key); + if (!wrenGetMapContainsKey(ptr.get(), 0, 1)) { + throw NotFound(); + } + wrenGetMapValue(ptr.get(), 0, 1, 2); + return detail::PopHelper::f(ptr.get(), 2); + } else { + throw RuntimeError("Invalid handle"); + } + } + + /*! + * @brief Erases a key from the map + * @throws NotFound if the key does not exist in the map + * @throws RuntimeError if this is an invalid map or the Wren VM has terminated + * @returns true if the key has been erased, otherwise false + * @warning If using strings, make sure that you use std::string because + * raw C-strings are not allowed. + * @note This function accepts any type you want. Integers, strings, booleans, + * does not matter as long as Wren map supports that key type. + */ + template + bool erase(const Key& key) const { + if (const auto ptr = handle->getVmWeak().lock()) { + wrenEnsureSlots(ptr.get(), 3); + wrenSetSlotHandle(ptr.get(), 0, handle->getHandle()); + detail::PushHelper::f(ptr.get(), 1, key); + wrenRemoveMapValue(ptr.get(), 0, 1, 2); + return !detail::is(ptr.get(), 2); + } else { + throw RuntimeError("Invalid handle"); + } + } + + /*! + * @brief Returns the size of the map + * @throws RuntimeError if this is an invalid map or the Wren VM has terminated + */ + size_t count() const { + if (const auto ptr = handle->getVmWeak().lock()) { + wrenEnsureSlots(ptr.get(), 1); + wrenSetSlotHandle(ptr.get(), 0, handle->getHandle()); + return wrenGetMapCount(ptr.get(), 0); + } else { + throw RuntimeError("Invalid handle"); + } + } + +private: + std::shared_ptr handle; +}; + +template <> +inline Map detail::getSlot(WrenVM* vm, const int idx) { + validate(vm, idx); + return Map(std::make_shared(getSharedVm(vm), wrenGetSlotHandle(vm, idx))); +} + +template <> +inline bool detail::is(WrenVM* vm, const int idx) { + return wrenGetSlotType(vm, idx) == WREN_TYPE_MAP; +} + +template <> +inline void detail::PushHelper::f(WrenVM* vm, int idx, const Map& value) { + wrenSetSlotHandle(value.getHandle().getVm(), idx, value.getHandle().getHandle()); +} + +template <> +inline void detail::PushHelper::f(WrenVM* vm, int idx, Map&& value) { + wrenSetSlotHandle(value.getHandle().getVm(), idx, value.getHandle().getHandle()); +} + +template <> +inline void detail::PushHelper::f(WrenVM* vm, int idx, const Map value) { + wrenSetSlotHandle(value.getHandle().getVm(), idx, value.getHandle().getHandle()); +} + +template <> +inline void detail::PushHelper::f(WrenVM* vm, int idx, const Map& value) { + wrenSetSlotHandle(value.getHandle().getVm(), idx, value.getHandle().getHandle()); +} + +template <> +inline void detail::PushHelper::f(WrenVM* vm, int idx, Map& value) { + wrenSetSlotHandle(value.getHandle().getVm(), idx, value.getHandle().getHandle()); +} +} // namespace wrenbind17 diff --git a/be/src/thirdparty/wrenbind17/wrenbind17/method.hpp b/be/src/thirdparty/wrenbind17/wrenbind17/method.hpp new file mode 100644 index 00000000000..0444e26e16c --- /dev/null +++ b/be/src/thirdparty/wrenbind17/wrenbind17/method.hpp @@ -0,0 +1,78 @@ +#pragma once + +#include +#include + +#include "any.hpp" +#include "exception.hpp" + +/** + * @ingroup wrenbind17 + */ +namespace wrenbind17 { +#ifndef DOXYGEN_SHOULD_SKIP_THIS +namespace detail { +inline void pushArgs(WrenVM* vm, int idx) { + (void)vm; + (void)idx; +} + +template +inline void pushArgs(WrenVM* vm, int idx, First&& first, Other&&... other) { + PushHelper::f(vm, idx, std::forward(first)); + pushArgs(vm, ++idx, std::forward(other)...); +} + +template +struct CallAndReturn { + static Any func(WrenVM* vm, WrenHandle* handle, WrenHandle* func, Args&&... args) { + constexpr auto n = sizeof...(Args); + wrenEnsureSlots(vm, n + 1); + wrenSetSlotHandle(vm, 0, handle); + + pushArgs(vm, 1, std::forward(args)...); + + if (wrenCall(vm, func) != WREN_RESULT_SUCCESS) { + throw RuntimeError(getLastError(vm)); + } + + return getSlot(vm, 0); + } +}; +} // namespace detail +#endif + +/** + * @ingroup wrenbind17 + */ +class Method { +public: + Method() = default; + + Method(std::shared_ptr variable, std::shared_ptr handle) + : variable(std::move(variable)), handle(std::move(handle)) {} + + ~Method() { reset(); } + + template + Any operator()(Args&&... args) { + if (const auto ptr = handle->getVmWeak().lock().get()) { + return detail::CallAndReturn::func(ptr, variable->getHandle(), handle->getHandle(), + std::forward(args)...); + } else { + throw RuntimeError("Invalid handle"); + } + } + + operator bool() const { return variable && handle; } + + void reset() { + handle.reset(); + variable.reset(); + } + +private: + std::shared_ptr variable; + std::shared_ptr handle; +}; +} // namespace wrenbind17 diff --git a/be/src/thirdparty/wrenbind17/wrenbind17/module.hpp b/be/src/thirdparty/wrenbind17/wrenbind17/module.hpp new file mode 100644 index 00000000000..b5166b6823c --- /dev/null +++ b/be/src/thirdparty/wrenbind17/wrenbind17/module.hpp @@ -0,0 +1,89 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "foreign.hpp" + +/** + * @ingroup wrenbind17 + */ +namespace wrenbind17 { +void addClassType(WrenVM* vm, const std::string& module, const std::string& name, size_t hash); +void addClassCast(WrenVM* vm, std::shared_ptr convertor, size_t hash, size_t other); + +/** + * @ingroup wrenbind17 + */ +class ForeignModule { +public: + ForeignModule(std::string name, WrenVM* vm) : name(std::move(name)), vm(vm) {} + ForeignModule(const ForeignModule& other) = delete; + ForeignModule(ForeignModule&& other) noexcept : vm(nullptr) { swap(other); } + ~ForeignModule() = default; + ForeignModule& operator=(const ForeignModule& other) = delete; + ForeignModule& operator=(ForeignModule&& other) noexcept { + if (this != &other) { + swap(other); + } + return *this; + } + void swap(ForeignModule& other) { + std::swap(klasses, other.klasses); + std::swap(vm, other.vm); + std::swap(name, other.name); + } + + template + ForeignKlassImpl& klass(std::string name) { + insertKlassCast(); + auto ptr = std::make_unique>(std::move(name)); + auto ret = ptr.get(); + addClassType(vm, this->name, ptr->getName(), typeid(T).hash_code()); + klasses.insert(std::make_pair(ptr->getName(), std::move(ptr))); + return *ret; + } + + std::string str() const { + std::stringstream ss; + for (const auto& pair : klasses) { + pair.second->generate(ss); + } + for (const auto& r : raw) { + ss << r << "\n"; + } + return ss.str(); + } + + void append(std::string text) { raw.push_back(std::move(text)); } + + ForeignKlass& findKlass(const std::string& name) { + auto it = klasses.find(name); + if (it == klasses.end()) throw NotFound(); + return *it->second; + } + + const std::string& getName() const { return name; } + +private: + template + void insertKlassCast() { + // void + } + + template + void insertKlassCast() { + addClassCast(vm, std::make_shared>(), typeid(T).hash_code(), + typeid(Other).hash_code()); + insertKlassCast(); + } + + std::string name; + WrenVM* vm; + std::unordered_map> klasses; + std::vector raw; +}; +} // namespace wrenbind17 diff --git a/be/src/thirdparty/wrenbind17/wrenbind17/object.hpp b/be/src/thirdparty/wrenbind17/wrenbind17/object.hpp new file mode 100644 index 00000000000..59754e9c743 --- /dev/null +++ b/be/src/thirdparty/wrenbind17/wrenbind17/object.hpp @@ -0,0 +1,105 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "exception.hpp" +#include "handle.hpp" + +/** + * @ingroup wrenbind17 + */ +namespace wrenbind17 { +#ifndef DOXYGEN_SHOULD_SKIP_THIS +std::string getLastError(WrenVM* vm); + +inline void exceptionHandler(WrenVM* vm, const std::exception_ptr& eptr) { + try { + if (eptr) { + std::rethrow_exception(eptr); + } else { + wrenEnsureSlots(vm, 1); + wrenSetSlotString(vm, 0, "Unknown error"); + wrenAbortFiber(vm, 0); + } + } catch (std::exception& e) { + wrenEnsureSlots(vm, 1); + wrenSetSlotString(vm, 0, e.what()); + wrenAbortFiber(vm, 0); + } +} + +template +struct is_shared_ptr : std::false_type {}; +template +struct is_shared_ptr> : std::true_type {}; + +namespace detail { +class Foreign { +public: + Foreign() = default; + virtual ~Foreign() = 0; + virtual void* get() const = 0; + virtual size_t hash() const = 0; +}; + +inline Foreign::~Foreign() {} + +template +class ForeignObject : public Foreign { +public: + ForeignObject() {} + + ForeignObject(std::shared_ptr ptr) : ptr(std::move(ptr)) {} + + virtual ~ForeignObject() = default; + + void* get() const override { return ptr.get(); } + + size_t hash() const override { return typeid(T).hash_code(); } + + const std::shared_ptr& shared() const { return ptr; } + + std::shared_ptr ptr; +}; + +class ForeignPtrConvertor { +public: + ForeignPtrConvertor() = default; + virtual ~ForeignPtrConvertor() = default; +}; + +template +class ForeignSharedPtrConvertor : public ForeignPtrConvertor { +public: + ForeignSharedPtrConvertor() = default; + virtual ~ForeignSharedPtrConvertor() = default; + + virtual std::shared_ptr cast(Foreign* foreign) const = 0; +}; + +template +class ForeignObjectSharedPtrConvertor : public ForeignSharedPtrConvertor { +public: + ForeignObjectSharedPtrConvertor() = default; + virtual ~ForeignObjectSharedPtrConvertor() = default; + + inline std::shared_ptr cast(Foreign* foreign) const override { + if (!foreign) throw Exception("Cannot upcast foreign pointer is null and this should not happen"); + auto* ptr = dynamic_cast*>(foreign); + if (!ptr) throw BadCast("Bad cast while upcasting to a base type"); + return std::dynamic_pointer_cast(ptr->shared()); + } +}; + +template +struct is_shared_ptr : std::false_type {}; +template +struct is_shared_ptr> : std::true_type {}; +} // namespace detail +#endif +} // namespace wrenbind17 diff --git a/be/src/thirdparty/wrenbind17/wrenbind17/pop.hpp b/be/src/thirdparty/wrenbind17/wrenbind17/pop.hpp new file mode 100644 index 00000000000..7c5a0ddce44 --- /dev/null +++ b/be/src/thirdparty/wrenbind17/wrenbind17/pop.hpp @@ -0,0 +1,389 @@ +#pragma once + +#include +#include +#include + +#include "object.hpp" + +namespace wrenbind17 { +void getClassType(WrenVM* vm, std::string& module, std::string& name, size_t hash); +detail::ForeignPtrConvertor* getClassCast(WrenVM* vm, size_t hash, size_t other); + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +namespace detail { +// ============================================================================================================ +// CHECK SLOTS FOR TYPE +// ============================================================================================================ +inline const char* wrenSlotTypeToStr(const WrenType type) { + switch (type) { + case WREN_TYPE_BOOL: + return "bool"; + case WREN_TYPE_FOREIGN: + return "instance"; + case WREN_TYPE_LIST: + return "list"; + case WREN_TYPE_NULL: + return "null"; + case WREN_TYPE_NUM: + return "number"; + case WREN_TYPE_STRING: + return "string"; + case WREN_TYPE_UNKNOWN: + default: + return "unknown"; + } +} + +template +inline bool is(WrenVM* vm, const int idx) { + const auto type = wrenGetSlotType(vm, idx); + if (type != WrenType::WREN_TYPE_FOREIGN) return false; + + auto slot = wrenGetSlotForeign(vm, idx); + const auto foreign = reinterpret_cast(slot); + return foreign->hash() == typeid(T).hash_code(); +} + +template <> +inline bool is(WrenVM* vm, const int idx) { + return wrenGetSlotType(vm, idx) == WrenType::WREN_TYPE_BOOL; +} + +template <> +inline bool is(WrenVM* vm, const int idx) { + return wrenGetSlotType(vm, idx) == WrenType::WREN_TYPE_STRING; +} + +template <> +inline bool is(WrenVM* vm, const int idx) { + return wrenGetSlotType(vm, idx) == WrenType::WREN_TYPE_NULL; +} + +template <> +inline bool is(WrenVM* vm, const int idx) { + return wrenGetSlotType(vm, idx) == WrenType::WREN_TYPE_NUM; +} + +template <> +inline bool is(WrenVM* vm, const int idx) { + return wrenGetSlotType(vm, idx) == WrenType::WREN_TYPE_NUM; +} + +template <> +inline bool is(WrenVM* vm, const int idx) { + return wrenGetSlotType(vm, idx) == WrenType::WREN_TYPE_NUM; +} + +template <> +inline bool is(WrenVM* vm, const int idx) { + return wrenGetSlotType(vm, idx) == WrenType::WREN_TYPE_NUM; +} + +template <> +inline bool is(WrenVM* vm, const int idx) { + return wrenGetSlotType(vm, idx) == WrenType::WREN_TYPE_NUM; +} + +template <> +inline bool is(WrenVM* vm, const int idx) { + return wrenGetSlotType(vm, idx) == WrenType::WREN_TYPE_NUM; +} + +template <> +inline bool is(WrenVM* vm, const int idx) { + return wrenGetSlotType(vm, idx) == WrenType::WREN_TYPE_NUM; +} + +template <> +inline bool is(WrenVM* vm, const int idx) { + return wrenGetSlotType(vm, idx) == WrenType::WREN_TYPE_NUM; +} + +template <> +inline bool is(WrenVM* vm, const int idx) { + return wrenGetSlotType(vm, idx) == WrenType::WREN_TYPE_NUM; +} + +template <> +inline bool is(WrenVM* vm, const int idx) { + return wrenGetSlotType(vm, idx) == WrenType::WREN_TYPE_NUM; +} + +template <> +inline bool is(WrenVM* vm, const int idx) { + return wrenGetSlotType(vm, idx) == WrenType::WREN_TYPE_NUM; +} + +template <> +inline bool is(WrenVM* vm, const int idx) { + return wrenGetSlotType(vm, idx) == WrenType::WREN_TYPE_NUM; +} + +template <> +inline bool is(WrenVM* vm, const int idx) { + return wrenGetSlotType(vm, idx) == WrenType::WREN_TYPE_NUM; +} + +template +struct CheckSlot { + static bool f(WrenVM* vm, const int idx) { return is(vm, idx); } +}; + +template +struct CheckSlot { + static bool f(WrenVM* vm, const int idx) { return is(vm, idx); } +}; + +template +struct CheckSlot { + static bool f(WrenVM* vm, const int idx) { return is(vm, idx); } +}; + +template +struct CheckSlot { + static bool f(WrenVM* vm, const int idx) { return is(vm, idx); } +}; + +template +struct CheckSlot { + static bool f(WrenVM* vm, const int idx) { return is(vm, idx); } +}; + +template +struct CheckSlot> { + static bool f(WrenVM* vm, const int idx) { return is(vm, idx); } +}; + +template +struct CheckSlot&> { + static bool f(WrenVM* vm, const int idx) { return is(vm, idx); } +}; + +// ============================================================================================================ +// BASIC TYPES +// ============================================================================================================ +template +inline void validate(WrenVM* vm, int idx) { + const auto t = wrenGetSlotType(vm, idx); + if (t != Type) + throw BadCast("Bad cast when getting value from Wren got " + std::string(wrenSlotTypeToStr(t)) + " expected " + + std::string(wrenSlotTypeToStr(Type))); +} + +template +std::shared_ptr getSlotForeign(WrenVM* vm, void* slot) { + using Type = typename std::remove_const::type>::type; + using ForeignTypeConvertor = ForeignSharedPtrConvertor; + + const auto foreign = reinterpret_cast(slot); + if (foreign->hash() != typeid(Type).hash_code()) { + try { + auto base = getClassCast(vm, foreign->hash(), typeid(Type).hash_code()); + auto derived = reinterpret_cast(base); + if (!derived) { + throw BadCast("Bad cast the value cannot be upcast to the expected type"); + } + return derived->cast(foreign); + } catch (std::out_of_range& e) { + (void)e; + throw BadCast("Bad cast the value is not the expected type"); + } + } + + auto ptr = reinterpret_cast*>(foreign); + return ptr->shared(); +} + +template +const std::shared_ptr getSlotForeign(WrenVM* vm, const int idx) { + validate(vm, idx); + return getSlotForeign(vm, wrenGetSlotForeign(vm, idx)); +} + +template +T getSlot(WrenVM* vm, int idx) { + static_assert(!std::is_same(), "type can't be std::string"); + static_assert(!is_shared_ptr::value, "type can't be shared_ptr"); + validate(vm, idx); + return *getSlotForeign::type>(vm, idx).get(); +} + +template +struct PopHelper { + static inline T f(WrenVM* vm, int idx) { return getSlot(vm, idx); } +}; + +template +struct PopHelper { + static inline T* f(WrenVM* vm, int idx) { + const auto type = wrenGetSlotType(vm, idx); + if (type == WrenType::WREN_TYPE_NULL) + return nullptr; + else if (type != WrenType::WREN_TYPE_FOREIGN) + throw BadCast("Bad cast when getting value from Wren"); + return getSlotForeign::type>(vm, idx).get(); + } +}; + +template +struct PopHelper { + static inline const T& f(WrenVM* vm, int idx) { + static_assert(!std::is_same(), "type can't be std::string"); + static_assert(!std::is_same(), "type can't be std::nullptr_t"); + static_assert(!is_shared_ptr::value, "type can't be shared_ptr"); + return *getSlotForeign(vm, idx).get(); + } +}; + +template +struct PopHelper> { + static inline std::shared_ptr f(WrenVM* vm, int idx) { + const auto type = wrenGetSlotType(vm, idx); + if (type == WrenType::WREN_TYPE_NULL) + return nullptr; + else if (type != WrenType::WREN_TYPE_FOREIGN) + throw BadCast("Bad cast when getting value from Wren"); + return getSlotForeign(vm, idx); + } +}; + +template +struct PopHelper&> { + static inline std::shared_ptr f(WrenVM* vm, int idx) { + const auto type = wrenGetSlotType(vm, idx); + if (type == WrenType::WREN_TYPE_NULL) + return nullptr; + else if (type != WrenType::WREN_TYPE_FOREIGN) + throw BadCast("Bad cast when getting value from Wren"); + return getSlotForeign(vm, idx); + } +}; + +template <> +inline Handle getSlot(WrenVM* vm, int idx) { + validate(vm, idx); + return Handle(getSharedVm(vm), wrenGetSlotHandle(vm, idx)); +} + +template <> +inline std::string getSlot(WrenVM* vm, int idx) { + validate(vm, idx); + return std::string(wrenGetSlotString(vm, idx)); +} + +template <> +inline std::nullptr_t getSlot(WrenVM* vm, int idx) { + validate(vm, idx); + return nullptr; +} + +template <> +inline bool getSlot(WrenVM* vm, int idx) { + validate(vm, idx); + return wrenGetSlotBool(vm, idx); +} + +template <> +inline int8_t getSlot(WrenVM* vm, int idx) { + validate(vm, idx); + return static_cast(wrenGetSlotDouble(vm, idx)); +} + +template <> +inline char getSlot(WrenVM* vm, int idx) { + validate(vm, idx); + return static_cast(wrenGetSlotDouble(vm, idx)); +} + +template <> +inline int getSlot(WrenVM* vm, int idx) { + validate(vm, idx); + return static_cast(wrenGetSlotDouble(vm, idx)); +} + +template <> +inline short getSlot(WrenVM* vm, int idx) { + validate(vm, idx); + return static_cast(wrenGetSlotDouble(vm, idx)); +} + +template <> +inline long getSlot(WrenVM* vm, int idx) { + validate(vm, idx); + return static_cast(wrenGetSlotDouble(vm, idx)); +} + +template <> +inline unsigned char getSlot(WrenVM* vm, int idx) { + validate(vm, idx); + return static_cast(wrenGetSlotDouble(vm, idx)); +} + +template <> +inline unsigned long getSlot(WrenVM* vm, int idx) { + validate(vm, idx); + return static_cast(wrenGetSlotDouble(vm, idx)); +} + +template <> +inline unsigned short getSlot(WrenVM* vm, int idx) { + validate(vm, idx); + return static_cast(wrenGetSlotDouble(vm, idx)); +} + +template <> +inline unsigned getSlot(WrenVM* vm, int idx) { + validate(vm, idx); + return static_cast(wrenGetSlotDouble(vm, idx)); +} + +template <> +inline long long getSlot(WrenVM* vm, int idx) { + validate(vm, idx); + return static_cast(wrenGetSlotDouble(vm, idx)); +} + +template <> +inline unsigned long long getSlot(WrenVM* vm, int idx) { + validate(vm, idx); + return static_cast(wrenGetSlotDouble(vm, idx)); +} + +template <> +inline float getSlot(WrenVM* vm, int idx) { + validate(vm, idx); + return static_cast(wrenGetSlotDouble(vm, idx)); +} + +template <> +inline double getSlot(WrenVM* vm, int idx) { + validate(vm, idx); + return static_cast(wrenGetSlotDouble(vm, idx)); +} + +#define WRENBIND17_POP_HELPER(Type) \ + template <> \ + struct PopHelper { \ + static inline Type f(WrenVM* vm, int idx) { return getSlot(vm, idx); } \ + }; + +WRENBIND17_POP_HELPER(std::string) +WRENBIND17_POP_HELPER(std::nullptr_t) +WRENBIND17_POP_HELPER(bool) +WRENBIND17_POP_HELPER(int8_t) +WRENBIND17_POP_HELPER(char) +WRENBIND17_POP_HELPER(int) +WRENBIND17_POP_HELPER(short) +WRENBIND17_POP_HELPER(long) +WRENBIND17_POP_HELPER(unsigned long) +WRENBIND17_POP_HELPER(unsigned) +WRENBIND17_POP_HELPER(long long) +WRENBIND17_POP_HELPER(unsigned long long) +WRENBIND17_POP_HELPER(unsigned short) +WRENBIND17_POP_HELPER(unsigned char) +WRENBIND17_POP_HELPER(float) +WRENBIND17_POP_HELPER(double) +} // namespace detail +#endif +} // namespace wrenbind17 diff --git a/be/src/thirdparty/wrenbind17/wrenbind17/push.hpp b/be/src/thirdparty/wrenbind17/wrenbind17/push.hpp new file mode 100644 index 00000000000..f591abf42a8 --- /dev/null +++ b/be/src/thirdparty/wrenbind17/wrenbind17/push.hpp @@ -0,0 +1,296 @@ +#pragma once + +#include +#include + +#include "exception.hpp" +#include "object.hpp" + +namespace wrenbind17 { +#ifndef DOXYGEN_SHOULD_SKIP_THIS +void getClassType(WrenVM* vm, std::string& module, std::string& name, size_t hash); +bool isClassRegistered(WrenVM* vm, const size_t hash); +detail::ForeignPtrConvertor* getClassCast(WrenVM* vm, size_t hash, size_t other); + +namespace detail { +template +struct PushHelper; + +template +void pushAsConstRef(WrenVM* vm, int idx, const T& value) { + static_assert(!std::is_same::type>(), "type can't be int"); + static_assert(!std::is_same::type>(), "type can't be std::string"); + static_assert(!is_shared_ptr::value, "type can't be shared_ptr"); + try { + std::string module; + std::string klass; + getClassType(vm, module, klass, typeid(T).hash_code()); + + wrenEnsureSlots(vm, idx + 1); + wrenGetVariable(vm, module.c_str(), klass.c_str(), idx); + + auto memory = wrenSetSlotNewForeign(vm, idx, idx, sizeof(ForeignObject)); + auto* foreign = new (memory) ForeignObject(std::make_shared(value)); + (void)foreign; + } catch (std::out_of_range& e) { + (void)e; + throw BadCast("Class type not registered in Wren VM"); + } +} + +template +void pushAsMove(WrenVM* vm, int idx, T&& value) { + static_assert(!std::is_same(), "type can't be int"); + static_assert(!std::is_same(), "type can't be std::string"); + static_assert(!is_shared_ptr::value, "type can't be shared_ptr"); + try { + std::string module; + std::string klass; + getClassType(vm, module, klass, typeid(T).hash_code()); + + wrenEnsureSlots(vm, idx + 1); + wrenGetVariable(vm, module.c_str(), klass.c_str(), idx); + + auto memory = wrenSetSlotNewForeign(vm, idx, idx, sizeof(ForeignObject)); + auto* foreign = new (memory) ForeignObject(std::make_shared(std::move(value))); + (void)foreign; + } catch (std::out_of_range& e) { + (void)e; + throw BadCast("Class type not registered in Wren VM"); + } +} + +template +void pushAsPtr(WrenVM* vm, int idx, T* value) { + static_assert(!std::is_same(), "type can't be int"); + static_assert(!std::is_same(), "type can't be std::string"); + static_assert(!is_shared_ptr::value, "type can't be shared_ptr"); + try { + std::string module; + std::string klass; + getClassType(vm, module, klass, typeid(T).hash_code()); + + wrenEnsureSlots(vm, idx + 1); + wrenGetVariable(vm, module.c_str(), klass.c_str(), idx); + + auto memory = wrenSetSlotNewForeign(vm, idx, idx, sizeof(ForeignObject)); + auto* foreign = new (memory) ForeignObject(std::shared_ptr(value, [](T* t) {})); + (void)foreign; + } catch (std::out_of_range& e) { + (void)e; + throw BadCast("Class type not registered in Wren VM"); + } +} + +template +struct PushHelper { + static inline void f(WrenVM* vm, int idx, const T& value) { pushAsConstRef(vm, idx, value); } + + static inline void f(WrenVM* vm, int idx, T&& value) { pushAsMove(vm, idx, std::move(value)); } +}; + +template +struct PushHelper { + static inline void f(WrenVM* vm, int idx, const T value) { PushHelper::f(vm, idx, value); } +}; + +template +struct PushHelper { + static inline void f(WrenVM* vm, int idx, T* value) { pushAsPtr(vm, idx, value); } +}; + +template +struct PushHelper { + static inline void f(WrenVM* vm, int idx, const T* value) { PushHelper::f(vm, idx, const_cast(value)); } +}; + +template +struct PushHelper { + static inline void f(WrenVM* vm, int idx, const T*& value) { PushHelper::f(vm, idx, const_cast(value)); } +}; + +template +struct PushHelper { + static inline void f(WrenVM* vm, int idx, T& value) { PushHelper::f(vm, idx, static_cast(value)); } +}; + +template +struct PushHelper { + static inline void f(WrenVM* vm, int idx, const T& value) { + PushHelper::f(vm, idx, static_cast(value)); + } +}; + +// ============================================================================================================ +// BASIC TYPES +// ============================================================================================================ + +#define WRENBIND17_PUSH_HELPER(T, FUNC) \ + template <> \ + inline void PushHelper::f(WrenVM* vm, int idx, const T& value) { \ + FUNC; \ + } \ + template <> \ + inline void PushHelper::f(WrenVM* vm, int idx, T&& value) { \ + FUNC; \ + } \ + template <> \ + inline void PushHelper::f(WrenVM* vm, int idx, const T value) { \ + FUNC; \ + } \ + template <> \ + inline void PushHelper::f(WrenVM* vm, int idx, T& value) { \ + FUNC; \ + } \ + template <> \ + inline void PushHelper::f(WrenVM* vm, int idx, const T& value) { \ + FUNC; \ + } \ + template <> \ + inline void PushHelper::f(WrenVM* vm, int idx, T* value) { \ + PushHelper::f(vm, idx, *value); \ + } \ + template <> \ + inline void PushHelper::f(WrenVM* vm, int idx, const T* value) { \ + PushHelper::f(vm, idx, *value); \ + } + +WRENBIND17_PUSH_HELPER(char, wrenSetSlotDouble(vm, idx, static_cast(value))); +WRENBIND17_PUSH_HELPER(int8_t, wrenSetSlotDouble(vm, idx, static_cast(value))); +WRENBIND17_PUSH_HELPER(short, wrenSetSlotDouble(vm, idx, static_cast(value))); +WRENBIND17_PUSH_HELPER(int, wrenSetSlotDouble(vm, idx, static_cast(value))); +WRENBIND17_PUSH_HELPER(long, wrenSetSlotDouble(vm, idx, static_cast(value))); +WRENBIND17_PUSH_HELPER(long long, wrenSetSlotDouble(vm, idx, static_cast(value))); +WRENBIND17_PUSH_HELPER(uint8_t, wrenSetSlotDouble(vm, idx, static_cast(value))); +WRENBIND17_PUSH_HELPER(unsigned short, wrenSetSlotDouble(vm, idx, static_cast(value))); +WRENBIND17_PUSH_HELPER(unsigned int, wrenSetSlotDouble(vm, idx, static_cast(value))); +WRENBIND17_PUSH_HELPER(unsigned long, wrenSetSlotDouble(vm, idx, static_cast(value))); +WRENBIND17_PUSH_HELPER(unsigned long long, wrenSetSlotDouble(vm, idx, static_cast(value))); +WRENBIND17_PUSH_HELPER(float, wrenSetSlotDouble(vm, idx, static_cast(value))); +WRENBIND17_PUSH_HELPER(double, wrenSetSlotDouble(vm, idx, value)); +WRENBIND17_PUSH_HELPER(bool, wrenSetSlotBool(vm, idx, value)); +WRENBIND17_PUSH_HELPER(std::nullptr_t, wrenSetSlotNull(vm, idx)); + +template <> +struct PushHelper { + static inline void f(WrenVM* vm, int idx, const std::string value) { wrenSetSlotString(vm, idx, value.c_str()); } +}; + +template +struct PushHelper { + static inline void f(WrenVM* vm, int idx, const char (&value)[N]) { wrenSetSlotString(vm, idx, value); } +}; + +template +struct PushHelper { + static inline void f(WrenVM* vm, int idx, char (&value)[N]) { wrenSetSlotString(vm, idx, value); } +}; + +template <> +struct PushHelper { + static inline void f(WrenVM* vm, int idx, const char*& value) { wrenSetSlotString(vm, idx, value); } +}; + +template <> +struct PushHelper { + static inline void f(WrenVM* vm, int idx, char*& value) { wrenSetSlotString(vm, idx, value); } +}; + +template <> +struct PushHelper { + static inline void f(WrenVM* vm, int idx, const std::string value) { wrenSetSlotString(vm, idx, value.c_str()); } +}; + +template <> +struct PushHelper { + static inline void f(WrenVM* vm, int idx, std::string&& value) { wrenSetSlotString(vm, idx, value.c_str()); } +}; + +template <> +struct PushHelper { + static inline void f(WrenVM* vm, int idx, std::string& value) { wrenSetSlotString(vm, idx, value.c_str()); } +}; + +template <> +struct PushHelper { + static inline void f(WrenVM* vm, int idx, const std::string& value) { wrenSetSlotString(vm, idx, value.c_str()); } +}; + +template +struct PushHelper> { + static inline void f(WrenVM* vm, int idx, std::shared_ptr value) { + static_assert(!std::is_same(), "type can't be std::string"); + static_assert(!is_shared_ptr::value, "type can't be shared_ptr"); + try { + std::string module; + std::string klass; + getClassType(vm, module, klass, typeid(T).hash_code()); + + wrenEnsureSlots(vm, idx + 1); + wrenGetVariable(vm, module.c_str(), klass.c_str(), idx); + + auto memory = wrenSetSlotNewForeign(vm, idx, idx, sizeof(ForeignObject)); + auto* foreign = new (memory) ForeignObject(value); + (void)foreign; + } catch (std::out_of_range& e) { + (void)e; + throw BadCast("Class type not registered in Wren VM"); + } + } +}; + +template +struct PushHelper> { + static inline void f(WrenVM* vm, int idx, const std::shared_ptr value) { + PushHelper>::f(vm, idx, value); + } +}; + +template +struct PushHelper&&> { + static inline void f(WrenVM* vm, int idx, std::shared_ptr&& value) { + PushHelper>::f(vm, idx, std::move(value)); + } +}; + +template +struct PushHelper&> { + static inline void f(WrenVM* vm, int idx, std::shared_ptr& value) { + PushHelper>::f(vm, idx, value); + } +}; + +template +struct PushHelper&> { + static inline void f(WrenVM* vm, int idx, const std::shared_ptr& value) { + PushHelper>::f(vm, idx, value); + } +}; + +template +inline void loopAndPushIterable(WrenVM* vm, const int idx, Iter begin, Iter end) { + using T = typename std::iterator_traits::value_type; + wrenSetSlotNewList(vm, idx); + auto i = 0; + for (auto it = begin; it != end; ++it) { + PushHelper::f(vm, idx + 1, *it); + wrenInsertInList(vm, idx, i++, idx + 1); + } +} + +template +inline void loopAndPushKeyPair(WrenVM* vm, const int idx, Iter begin, Iter end) { + using T = typename std::iterator_traits::value_type; + using Key = typename T::first_type; + using Value = typename T::second_type; + wrenSetSlotNewMap(vm, idx); + wrenEnsureSlots(vm, 3); + for (auto it = begin; it != end; ++it) { + PushHelper::f(vm, idx + 1, std::forward(it->first)); + PushHelper::f(vm, idx + 2, std::forward(it->second)); + wrenSetMapValue(vm, idx, idx + 1, idx + 2); + } +} +} // namespace detail +#endif +} // namespace wrenbind17 diff --git a/be/src/thirdparty/wrenbind17/wrenbind17/std.hpp b/be/src/thirdparty/wrenbind17/wrenbind17/std.hpp new file mode 100644 index 00000000000..12e571d7fff --- /dev/null +++ b/be/src/thirdparty/wrenbind17/wrenbind17/std.hpp @@ -0,0 +1,372 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "module.hpp" + +namespace wrenbind17 { +#ifndef DOXYGEN_SHOULD_SKIP_THIS +namespace detail { +template +struct is_equality_comparable : std::false_type {}; + +template +struct is_equality_comparable< + T, typename std::enable_if() == std::declval(), (void)0)>::type> + : std::true_type {}; +} // namespace detail + +template +class StdVectorHelper; + +template +class StdVectorHelper::value>::type> { +public: + static bool contains(std::vector& self, const T& value) { + return std::find(self.begin(), self.end(), value) != self.end(); + } +}; + +template +class StdVectorHelper::value>::type> { +public: + static bool contains(std::vector& self, const T& value) { + return std::find_if(self.begin(), self.end(), [&](const T& e) -> bool { return &e == &value; }) != self.end(); + } +}; +#endif + +template +class StdVectorBindings { +public: + typedef typename std::vector::iterator Iterator; + typedef typename std::vector Vector; + + static void setIndex(Vector& self, size_t index, T value) { self[index] = std::move(value); } + + static const T& getIndex(Vector& self, size_t index) { return self[index]; } + + static void add(Vector& self, T value) { self.push_back(std::move(value)); } + + static std::variant iterate(Vector& self, std::variant other) { + if (other.index() == 1) { + auto it = std::get(other); + ++it; + if (it != self.end()) { + return {it}; + } + + return {false}; + } else { + if (self.empty()) return {false}; + return {self.begin()}; + } + } + + static const T& iteratorValue(Vector& self, std::shared_ptr other) { + auto& it = *other; + return *it; + } + + static size_t count(Vector& self) { return self.size(); } + + static T removeAt(Vector& self, int32_t index) { + if (index == -1) { + auto ret = std::move(self.back()); + self.pop_back(); + return std::move(ret); + } else { + if (index < 0) { + index = static_cast(self.size()) + index; + } + + if (index > static_cast(self.size())) { + throw std::out_of_range("invalid index"); + } else if (index == static_cast(self.size())) { + auto ret = std::move(self.back()); + self.pop_back(); + return std::move(ret); + } else { + auto ret = std::move(self.at(index)); + self.erase(self.begin() + index); + return std::move(ret); + } + } + } + + static void insert(Vector& self, int32_t index, T value) { + if (index == -1) { + self.push_back(std::move(value)); + } else { + if (index < 0) { + index = static_cast(self.size()) + index; + } + + if (index > static_cast(self.size())) { + throw std::out_of_range("invalid index"); + } else if (index == static_cast(self.size())) { + self.push_back(std::move(value)); + } else { + auto it = self.begin() + index; + self.insert(it, std::move(value)); + } + } + } + + static bool contains(Vector& self, const T& value) { + // return std::find(self.begin(), self.end(), value) != self.end(); + return StdVectorHelper::contains(self, value); + } + + static T pop(Vector& self) { + auto ret = std::move(self.back()); + self.pop_back(); + return std::move(ret); + } + + static void clear(Vector& self) { self.clear(); } + + static size_t size(Vector& self) { return self.size(); } + + static bool empty(Vector& self) { return self.empty(); } + + static void bind(ForeignModule& m, const std::string& name) { + auto& iter = m.klass(name + "Iter"); + iter.ctor(); + + auto& cls = m.klass(name); + cls.ctor(); + + cls.template funcExt<&StdVectorBindings::getIndex>(OPERATOR_GET_INDEX); + cls.template funcExt<&StdVectorBindings::setIndex>(OPERATOR_SET_INDEX); + cls.template funcExt<&StdVectorBindings::add>("add"); + cls.template funcExt<&StdVectorBindings::iterate>("iterate"); + cls.template funcExt<&StdVectorBindings::iteratorValue>("iteratorValue"); + cls.template funcExt<&StdVectorBindings::removeAt>("removeAt"); + cls.template funcExt<&StdVectorBindings::insert>("insert"); + cls.template funcExt<&StdVectorBindings::contains>("contains"); + cls.template funcExt<&StdVectorBindings::pop>("pop"); + cls.template funcExt<&StdVectorBindings::clear>("clear"); + cls.template funcExt<&StdVectorBindings::size>("size"); + cls.template funcExt<&StdVectorBindings::empty>("empty"); + cls.template propReadonlyExt<&StdVectorBindings::count>("count"); + } +}; + +template +class StdListBindings { +public: + typedef typename std::list::iterator Iterator; + typedef typename std::list List; + + static void setIndex(List& self, size_t index, T value) { + auto it = self.begin(); + std::advance(it, index); + *it = std::move(value); + } + + static const T& getIndex(List& self, size_t index) { + auto it = self.begin(); + std::advance(it, index); + return *it; + } + + static void add(List& self, T value) { self.push_back(std::move(value)); } + + static std::variant iterate(List& self, std::variant other) { + if (other.index() == 1) { + auto it = std::get(other); + ++it; + if (it != self.end()) { + return {it}; + } + + return {false}; + } else { + if (self.empty()) return {false}; + return {self.begin()}; + } + } + + static const T& iteratorValue(List& self, std::shared_ptr other) { + auto& it = *other; + return *it; + } + + static size_t count(List& self) { return self.size(); } + + static T removeAt(List& self, int32_t index) { + if (index == -1) { + auto ret = std::move(self.back()); + self.pop_back(); + return std::move(ret); + } else { + if (index < 0) { + index = static_cast(self.size()) + index; + } + + if (index > static_cast(self.size())) { + throw std::out_of_range("invalid index"); + } else if (index == static_cast(self.size())) { + auto ret = std::move(self.back()); + self.pop_back(); + return std::move(ret); + } else { + auto it = self.begin(); + std::advance(it, index); + auto ret = std::move(*it); + self.erase(it); + return std::move(ret); + } + } + } + + static void insert(List& self, int32_t index, T value) { + if (index == -1) { + self.push_back(std::move(value)); + } else { + if (index < 0) { + index = static_cast(self.size()) + index; + } + + if (index > static_cast(self.size())) { + throw std::out_of_range("invalid index"); + } else if (index == static_cast(self.size())) { + self.push_back(std::move(value)); + } else { + auto it = self.begin(); + std::advance(it, index); + self.insert(it, std::move(value)); + } + } + } + + static bool contains(List& self, const T& value) { + return std::find(self.begin(), self.end(), value) != self.end(); + } + + static T pop(List& self) { + auto ret = std::move(self.back()); + self.pop_back(); + return std::move(ret); + } + + static void clear(List& self) { self.clear(); } + + static size_t size(List& self) { return self.size(); } + + static bool empty(List& self) { return self.empty(); } + + static void bind(ForeignModule& m, const std::string& name) { + auto& iter = m.klass(name + "Iter"); + iter.ctor(); + + auto& cls = m.klass(name); + cls.ctor(); + + cls.template funcExt<&StdListBindings::getIndex>(OPERATOR_GET_INDEX); + cls.template funcExt<&StdListBindings::setIndex>(OPERATOR_SET_INDEX); + cls.template funcExt<&StdListBindings::add>("add"); + cls.template funcExt<&StdListBindings::iterate>("iterate"); + cls.template funcExt<&StdListBindings::iteratorValue>("iteratorValue"); + cls.template funcExt<&StdListBindings::removeAt>("removeAt"); + cls.template funcExt<&StdListBindings::insert>("insert"); + cls.template funcExt<&StdListBindings::contains>("contains"); + cls.template funcExt<&StdListBindings::pop>("pop"); + cls.template funcExt<&StdListBindings::clear>("clear"); + cls.template funcExt<&StdListBindings::size>("size"); + cls.template funcExt<&StdListBindings::empty>("empty"); + cls.template propReadonlyExt<&StdListBindings::count>("count"); + } +}; + +template +class AbstractMapBindings { +public: + typedef typename Map::key_type K; + typedef typename Map::mapped_type T; + typedef typename Map::iterator Iterator; + typedef typename Map::value_type Pair; + + static void setIndex(Map& self, const K& key, T value) { self[key] = std::move(value); } + + static T& getIndex(Map& self, const K& key) { return self[key]; } + + static std::variant remove(Map& self, const K& key) { + auto it = self.find(key); + if (it != self.end()) { + auto ret = std::move(it->second); + self.erase(it); + return {ret}; + } else { + return {nullptr}; + } + } + + static bool containsKey(Map& self, const K& key) { return self.find(key) != self.end(); } + + static size_t count(Map& self) { return self.size(); } + + static void clear(Map& self) { self.clear(); } + + static size_t size(Map& self) { return self.size(); } + + static bool empty(Map& self) { return self.empty(); } + + static std::variant iterate(Map& self, std::variant other) { + if (other.index() == 1) { + auto it = std::get(other); + ++it; + if (it != self.end()) { + return {it}; + } + + return {false}; + } else { + return {self.begin()}; + } + } + + static Pair iteratorValue(Map& self, std::shared_ptr other) { + auto& it = *other; + return *it; + } + + static const K& pairKey(Pair& pair) { return pair.first; } + + static const T& pairValue(Pair& pair) { return pair.second; } + + static void bind(ForeignModule& m, const std::string& name) { + auto& pair = m.klass(name + "Pair"); + pair.template propReadonlyExt<&AbstractMapBindings::pairKey>("key"); + pair.template propReadonlyExt<&AbstractMapBindings::pairValue>("value"); + + auto& iter = m.klass(name + "Iter"); + iter.ctor(); + + auto& cls = m.klass(name); + cls.ctor(); + + cls.template funcExt<&AbstractMapBindings::getIndex>(OPERATOR_GET_INDEX); + cls.template funcExt<&AbstractMapBindings::setIndex>(OPERATOR_SET_INDEX); + cls.template funcExt<&AbstractMapBindings::remove>("remove"); + cls.template funcExt<&AbstractMapBindings::containsKey>("containsKey"); + cls.template funcExt<&AbstractMapBindings::iterate>("iterate"); + cls.template funcExt<&AbstractMapBindings::iteratorValue>("iteratorValue"); + cls.template funcExt<&AbstractMapBindings::clear>("clear"); + cls.template funcExt<&AbstractMapBindings::size>("size"); + cls.template funcExt<&AbstractMapBindings::empty>("empty"); + cls.template propReadonlyExt<&AbstractMapBindings::count>("count"); + } +}; + +template +using StdMapBindings = AbstractMapBindings>; + +template +using StdUnorderedMapBindings = AbstractMapBindings>; +} // namespace wrenbind17 diff --git a/be/src/thirdparty/wrenbind17/wrenbind17/stddeque.hpp b/be/src/thirdparty/wrenbind17/wrenbind17/stddeque.hpp new file mode 100644 index 00000000000..5531933f10d --- /dev/null +++ b/be/src/thirdparty/wrenbind17/wrenbind17/stddeque.hpp @@ -0,0 +1,71 @@ +#pragma once + +#include +#include + +#include "pop.hpp" +#include "push.hpp" + +namespace wrenbind17 { +#ifndef DOXYGEN_SHOULD_SKIP_THIS +namespace detail { +template +struct PushHelper> { + static inline void f(WrenVM* vm, int idx, std::deque value) { + if (isClassRegistered(vm, typeid(std::deque).hash_code())) { + pushAsMove>(vm, idx, std::move(value)); + } else { + loopAndPushIterable(vm, idx, value.begin(), value.end()); + } + } +}; + +template +struct PushHelper*> { + static inline void f(WrenVM* vm, int idx, std::deque* value) { + if (isClassRegistered(vm, typeid(std::deque).hash_code())) { + pushAsPtr>(vm, idx, value); + } else { + loopAndPushIterable(vm, idx, value->begin(), value->end()); + } + } +}; + +template +struct PushHelper&> { + static inline void f(WrenVM* vm, int idx, const std::deque& value) { + if (isClassRegistered(vm, typeid(std::deque).hash_code())) { + pushAsConstRef>(vm, idx, value); + } else { + loopAndPushIterable(vm, idx, value.begin(), value.end()); + } + } +}; + +template +struct PopHelper&> { + static inline std::deque f(WrenVM* vm, const int idx) { + const auto type = wrenGetSlotType(vm, idx); + if (type == WrenType::WREN_TYPE_FOREIGN) { + return *getSlotForeign>(vm, idx).get(); + } + if (type != WrenType::WREN_TYPE_LIST) throw BadCast("Bad cast when getting value from Wren expected list"); + + std::deque res; + const auto size = wrenGetListCount(vm, idx); + wrenEnsureSlots(vm, 1); + for (size_t i = 0; i < size; i++) { + wrenGetListElement(vm, idx, static_cast(i), idx + 1); + res.push_back(PopHelper::f(vm, idx + 1)); + } + return res; + } +}; + +template +struct PopHelper> { + static inline std::deque f(WrenVM* vm, const int idx) { return PopHelper>::f(vm, idx); } +}; +} // namespace detail +#endif +} // namespace wrenbind17 diff --git a/be/src/thirdparty/wrenbind17/wrenbind17/stdlist.hpp b/be/src/thirdparty/wrenbind17/wrenbind17/stdlist.hpp new file mode 100644 index 00000000000..a18e5830c9c --- /dev/null +++ b/be/src/thirdparty/wrenbind17/wrenbind17/stdlist.hpp @@ -0,0 +1,71 @@ +#pragma once + +#include +#include + +#include "pop.hpp" +#include "push.hpp" + +namespace wrenbind17 { +#ifndef DOXYGEN_SHOULD_SKIP_THIS +namespace detail { +template +struct PushHelper> { + static inline void f(WrenVM* vm, int idx, std::list value) { + if (isClassRegistered(vm, typeid(std::list).hash_code())) { + pushAsMove>(vm, idx, std::move(value)); + } else { + loopAndPushIterable(vm, idx, value.begin(), value.end()); + } + } +}; + +template +struct PushHelper*> { + static inline void f(WrenVM* vm, int idx, std::list* value) { + if (isClassRegistered(vm, typeid(std::list).hash_code())) { + pushAsPtr>(vm, idx, value); + } else { + loopAndPushIterable(vm, idx, value->begin(), value->end()); + } + } +}; + +template +struct PushHelper&> { + static inline void f(WrenVM* vm, int idx, const std::list& value) { + if (isClassRegistered(vm, typeid(std::list).hash_code())) { + pushAsConstRef>(vm, idx, value); + } else { + loopAndPushIterable(vm, idx, value.begin(), value.end()); + } + } +}; + +template +struct PopHelper&> { + static inline std::list f(WrenVM* vm, const int idx) { + const auto type = wrenGetSlotType(vm, idx); + if (type == WrenType::WREN_TYPE_FOREIGN) { + return *getSlotForeign>(vm, idx).get(); + } + if (type != WrenType::WREN_TYPE_LIST) throw BadCast("Bad cast when getting value from Wren expected list"); + + std::list res; + const auto size = wrenGetListCount(vm, idx); + wrenEnsureSlots(vm, 1); + for (size_t i = 0; i < size; i++) { + wrenGetListElement(vm, idx, static_cast(i), idx + 1); + res.push_back(PopHelper::f(vm, idx + 1)); + } + return res; + } +}; + +template +struct PopHelper> { + static inline std::list f(WrenVM* vm, const int idx) { return PopHelper&>::f(vm, idx); } +}; +} // namespace detail +#endif +} // namespace wrenbind17 diff --git a/be/src/thirdparty/wrenbind17/wrenbind17/stdmap.hpp b/be/src/thirdparty/wrenbind17/wrenbind17/stdmap.hpp new file mode 100644 index 00000000000..bfb7c24f105 --- /dev/null +++ b/be/src/thirdparty/wrenbind17/wrenbind17/stdmap.hpp @@ -0,0 +1,80 @@ +#pragma once + +#include +#include +#include + +#include "pop.hpp" +#include "push.hpp" + +namespace wrenbind17 { +#ifndef DOXYGEN_SHOULD_SKIP_THIS +namespace detail { +template +struct PushHelper> { + static inline void f(WrenVM* vm, int idx, std::map value) { + if (isClassRegistered(vm, typeid(std::map).hash_code())) { + pushAsMove>(vm, idx, std::move(value)); + } else { + loopAndPushKeyPair(vm, idx, value.begin(), value.end()); + } + } +}; + +template +struct PushHelper*> { + static inline void f(WrenVM* vm, int idx, std::map* value) { + if (isClassRegistered(vm, typeid(std::map).hash_code())) { + pushAsPtr>(vm, idx, value); + } else { + loopAndPushKeyPair(vm, idx, value->begin(), value->end()); + } + } +}; + +template +struct PushHelper&> { + static inline void f(WrenVM* vm, int idx, const std::map& value) { + if (isClassRegistered(vm, typeid(std::map).hash_code())) { + pushAsConstRef>(vm, idx, value); + } else { + loopAndPushKeyPair(vm, idx, value.begin(), value.end()); + } + } +}; + +template +struct PushHelper> { + static inline void f(WrenVM* vm, int idx, std::unordered_map value) { + if (isClassRegistered(vm, typeid(std::unordered_map).hash_code())) { + pushAsMove>(vm, idx, std::move(value)); + } else { + loopAndPushKeyPair(vm, idx, value.begin(), value.end()); + } + } +}; + +template +struct PushHelper*> { + static inline void f(WrenVM* vm, int idx, std::unordered_map* value) { + if (isClassRegistered(vm, typeid(std::unordered_map).hash_code())) { + pushAsPtr>(vm, idx, value); + } else { + loopAndPushKeyPair(vm, idx, value->begin(), value->end()); + } + } +}; + +template +struct PushHelper&> { + static inline void f(WrenVM* vm, int idx, const std::unordered_map& value) { + if (isClassRegistered(vm, typeid(std::unordered_map).hash_code())) { + pushAsConstRef>(vm, idx, value); + } else { + loopAndPushKeyPair(vm, idx, value.begin(), value.end()); + } + } +}; +} // namespace detail +#endif +} // namespace wrenbind17 diff --git a/be/src/thirdparty/wrenbind17/wrenbind17/stdoptional.hpp b/be/src/thirdparty/wrenbind17/wrenbind17/stdoptional.hpp new file mode 100644 index 00000000000..f952efcfe15 --- /dev/null +++ b/be/src/thirdparty/wrenbind17/wrenbind17/stdoptional.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include +#include + +#include "pop.hpp" +#include "push.hpp" + +namespace wrenbind17 { +#ifndef DOXYGEN_SHOULD_SKIP_THIS +namespace detail { +template +struct PushHelper> { + inline static void f(WrenVM* vm, int idx, const std::optional& value) { + if (value.has_value()) { + PushHelper::f(vm, idx, value.value()); + } else { + PushHelper::f(vm, idx, nullptr); + } + } +}; + +template +struct PushHelper&> { + inline static void f(WrenVM* vm, int idx, const std::optional& value) { + PushHelper>::f(vm, idx, value); + } +}; + +template +struct PushHelper*> { + inline static void f(WrenVM* vm, int idx, const std::optional* value) { + PushHelper>::f(vm, idx, *value); + } +}; + +template +struct PushHelper&> { + inline static void f(WrenVM* vm, int idx, const std::optional& value) { + PushHelper>::f(vm, idx, value); + } +}; + +template +struct PopHelper> { + static inline std::optional f(WrenVM* vm, const int idx) { + if (is(vm, idx)) { + return std::nullopt; + } else { + return PopHelper::f(vm, idx); + } + } +}; + +template +struct PopHelper&> { + static inline std::optional f(WrenVM* vm, const int idx) { return PopHelper>::f(vm, idx); } +}; +} // namespace detail +#endif +} // namespace wrenbind17 diff --git a/be/src/thirdparty/wrenbind17/wrenbind17/stdset.hpp b/be/src/thirdparty/wrenbind17/wrenbind17/stdset.hpp new file mode 100644 index 00000000000..7580573e85c --- /dev/null +++ b/be/src/thirdparty/wrenbind17/wrenbind17/stdset.hpp @@ -0,0 +1,134 @@ +#pragma once + +#include +#include +#include + +#include "pop.hpp" +#include "push.hpp" + +namespace wrenbind17 { +#ifndef DOXYGEN_SHOULD_SKIP_THIS +namespace detail { +template +struct PushHelper> { + static inline void f(WrenVM* vm, int idx, std::set value) { + if (isClassRegistered(vm, typeid(std::set).hash_code())) { + pushAsMove>(vm, idx, std::move(value)); + } else { + loopAndPushIterable(vm, idx, value.begin(), value.end()); + } + } +}; + +template +struct PushHelper*> { + static inline void f(WrenVM* vm, int idx, std::set* value) { + if (isClassRegistered(vm, typeid(std::set).hash_code())) { + pushAsPtr>(vm, idx, value); + } else { + loopAndPushIterable(vm, idx, value->begin(), value->end()); + } + } +}; + +template +struct PushHelper&> { + static inline void f(WrenVM* vm, int idx, const std::set& value) { + if (isClassRegistered(vm, typeid(std::set).hash_code())) { + pushAsConstRef>(vm, idx, value); + } else { + loopAndPushIterable(vm, idx, value.begin(), value.end()); + } + } +}; + +template +struct PopHelper&> { + static inline std::set f(WrenVM* vm, const int idx) { + const auto type = wrenGetSlotType(vm, idx); + if (type == WrenType::WREN_TYPE_FOREIGN) { + return *getSlotForeign>(vm, idx).get(); + } + if (type != WrenType::WREN_TYPE_LIST) throw BadCast("Bad cast when getting value from Wren expected list"); + + std::set res; + const auto size = wrenGetListCount(vm, idx); + wrenEnsureSlots(vm, 1); + res.reserve(size); + for (size_t i = 0; i < size; i++) { + wrenGetListElement(vm, idx, static_cast(i), idx + 1); + res.insert(PopHelper::f(vm, idx + 1)); + } + return res; + } +}; + +template +struct PopHelper> { + static inline std::set f(WrenVM* vm, const int idx) { return PopHelper&>::f(vm, idx); } +}; + +template +struct PushHelper> { + static inline void f(WrenVM* vm, int idx, std::unordered_set value) { + if (isClassRegistered(vm, typeid(std::unordered_set).hash_code())) { + pushAsMove>(vm, idx, std::move(value)); + } else { + loopAndPushIterable(vm, idx, value.begin(), value.end()); + } + } +}; + +template +struct PushHelper*> { + static inline void f(WrenVM* vm, int idx, std::unordered_set* value) { + if (isClassRegistered(vm, typeid(std::unordered_set).hash_code())) { + pushAsPtr>(vm, idx, value); + } else { + loopAndPushIterable(vm, idx, value->begin(), value->end()); + } + } +}; + +template +struct PushHelper&> { + static inline void f(WrenVM* vm, int idx, const std::unordered_set& value) { + if (isClassRegistered(vm, typeid(std::unordered_set).hash_code())) { + pushAsConstRef>(vm, idx, value); + } else { + loopAndPushIterable(vm, idx, value.begin(), value.end()); + } + } +}; + +template +struct PopHelper&> { + static inline std::set f(WrenVM* vm, const int idx) { + const auto type = wrenGetSlotType(vm, idx); + if (type == WrenType::WREN_TYPE_FOREIGN) { + return *getSlotForeign>(vm, idx).get(); + } + if (type != WrenType::WREN_TYPE_LIST) throw BadCast("Bad cast when getting value from Wren expected list"); + + std::unordered_set res; + const auto size = wrenGetListCount(vm, idx); + wrenEnsureSlots(vm, 1); + res.reserve(size); + for (size_t i = 0; i < size; i++) { + wrenGetListElement(vm, idx, static_cast(i), idx + 1); + res.insert(PopHelper::f(vm, idx + 1)); + } + return res; + } +}; + +template +struct PopHelper> { + static inline std::set f(WrenVM* vm, const int idx) { + return PopHelper&>::f(vm, idx); + } +}; +} // namespace detail +#endif +} // namespace wrenbind17 diff --git a/be/src/thirdparty/wrenbind17/wrenbind17/stdvariant.hpp b/be/src/thirdparty/wrenbind17/wrenbind17/stdvariant.hpp new file mode 100644 index 00000000000..bc50e6cecd6 --- /dev/null +++ b/be/src/thirdparty/wrenbind17/wrenbind17/stdvariant.hpp @@ -0,0 +1,84 @@ +#pragma once + +#include +#include + +#include "pop.hpp" +#include "push.hpp" + +namespace wrenbind17 { +#ifndef DOXYGEN_SHOULD_SKIP_THIS +namespace detail { +template +inline void loopAndPushVariant(WrenVM* vm, int idx, const VariantType& v, size_t i) { + PushHelper::f(vm, idx, nullptr); +} + +template +inline void loopAndPushVariant(WrenVM* vm, int idx, const VariantType& v, size_t i) { + if (v.index() == i) { + PushHelper::f(vm, idx, std::get(v)); + } else { + loopAndPushVariant(vm, idx, v, i + 1); + } +} + +template +struct PushHelper> { + inline static void f(WrenVM* vm, int idx, const std::variant& value) { + loopAndPushVariant, Ts...>(vm, idx, value, 0); + } +}; + +template +struct PushHelper&> { + inline static void f(WrenVM* vm, int idx, const std::variant& value) { + PushHelper>::f(vm, idx, value); + } +}; + +template +struct PushHelper*> { + inline static void f(WrenVM* vm, int idx, const std::variant* value) { + PushHelper>::f(vm, idx, *value); + } +}; + +template +struct PushHelper&> { + inline static void f(WrenVM* vm, int idx, const std::variant& value) { + PushHelper>::f(vm, idx, value); + } +}; + +template +VariantType loopAndFindVariant(WrenVM* vm, int idx) { + throw BadCast("Bad cast when getting variant from Wren"); +} + +template +VariantType loopAndFindVariant(WrenVM* vm, const int idx) { + if (CheckSlot::f(vm, idx)) { + return {PopHelper::f(vm, idx)}; + } + return loopAndFindVariant(vm, idx); +} + +template +struct PopHelper> { + static inline std::variant f(WrenVM* vm, const int idx) { + using VariantType = typename std::variant; + return loopAndFindVariant(vm, idx); + } +}; + +template +struct PopHelper&> { + static inline std::variant f(WrenVM* vm, const int idx) { + using VariantType = typename std::variant; + return loopAndFindVariant(vm, idx); + } +}; +} // namespace detail +#endif +} // namespace wrenbind17 diff --git a/be/src/thirdparty/wrenbind17/wrenbind17/stdvector.hpp b/be/src/thirdparty/wrenbind17/wrenbind17/stdvector.hpp new file mode 100644 index 00000000000..f886097697d --- /dev/null +++ b/be/src/thirdparty/wrenbind17/wrenbind17/stdvector.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include +#include + +#include "pop.hpp" +#include "push.hpp" + +namespace wrenbind17 { +#ifndef DOXYGEN_SHOULD_SKIP_THIS +namespace detail { +template +struct PushHelper> { + static inline void f(WrenVM* vm, int idx, std::vector value) { + if (isClassRegistered(vm, typeid(std::vector).hash_code())) { + pushAsMove>(vm, idx, std::move(value)); + } else { + loopAndPushIterable(vm, idx, value.begin(), value.end()); + } + } +}; + +template +struct PushHelper*> { + static inline void f(WrenVM* vm, int idx, std::vector* value) { + if (isClassRegistered(vm, typeid(std::vector).hash_code())) { + pushAsPtr>(vm, idx, value); + } else { + loopAndPushIterable(vm, idx, value->begin(), value->end()); + } + } +}; + +template +struct PushHelper&> { + static inline void f(WrenVM* vm, int idx, const std::vector& value) { + if (isClassRegistered(vm, typeid(std::vector).hash_code())) { + pushAsConstRef>(vm, idx, value); + } else { + loopAndPushIterable(vm, idx, value.begin(), value.end()); + } + } +}; + +template +struct PopHelper&> { + static inline std::vector f(WrenVM* vm, const int idx) { + const auto type = wrenGetSlotType(vm, idx); + if (type == WrenType::WREN_TYPE_FOREIGN) { + return *getSlotForeign>(vm, idx).get(); + } + if (type != WrenType::WREN_TYPE_LIST) throw BadCast("Bad cast when getting value from Wren expected list"); + + std::vector res; + const auto size = wrenGetListCount(vm, idx); + wrenEnsureSlots(vm, 1); + res.reserve(size); + for (size_t i = 0; i < size; i++) { + wrenGetListElement(vm, idx, static_cast(i), idx + 1); + res.push_back(PopHelper::f(vm, idx + 1)); + } + return res; + } +}; + +template +struct PopHelper> { + static inline std::vector f(WrenVM* vm, const int idx) { return PopHelper&>::f(vm, idx); } +}; +} // namespace detail +#endif +} // namespace wrenbind17 diff --git a/be/src/thirdparty/wrenbind17/wrenbind17/variable.hpp b/be/src/thirdparty/wrenbind17/wrenbind17/variable.hpp new file mode 100644 index 00000000000..89d3cec38fd --- /dev/null +++ b/be/src/thirdparty/wrenbind17/wrenbind17/variable.hpp @@ -0,0 +1,97 @@ +#pragma once + +#include + +#include "exception.hpp" +#include "method.hpp" +#include "pop.hpp" +#include "push.hpp" + +/** + * @ingroup wrenbind17 + */ +namespace wrenbind17 { +/** + * @ingroup wrenbind17 + * @brief Holds some Wren variable which can be a class or class instance + * @details You can use this to pass around Wren classes or class instances. + * You can also use this to get Wren class methods. You can also call a Wren + * function from C++ side and pass this Variable into Wren. To get this variable, + * either call a Wren function that returns some class (or class instance), or + * use wrenbind17::VM::find() function that looks up a class (or class instance) + * based on the module name. + * @note This variable can safely outlive the wrenbind17::VM class. If that happens + * then functions of this class will throw wrenbind17::RuntimeError exception. This + * holder will not try to free the Wren variable if the VM has been terminated. You + * don't have to worry about the lifetime of this holder. (uses weak pointers). + */ +class Variable { +public: + Variable() {} + Variable(const std::shared_ptr& handle) : handle(handle) {} + ~Variable() { reset(); } + + /*! + * @brief Looks up a function from this Wren variable. + * @details The signature must match Wren function signature. + * For example: `main()` or `foo(_,_)` etc. Use underscores to specify + * parameters of that function. + * @throws RuntimeError if this variable is invalid or the Wren VM has terminated. + */ + Method func(const std::string& signature) { + if (const auto ptr = handle->getVmWeak().lock()) { + auto* h = wrenMakeCallHandle(ptr.get(), signature.c_str()); + return Method(handle, std::make_shared(ptr, h)); + } else { + throw RuntimeError("Invalid handle"); + } + } + + Handle& getHandle() { return *handle; } + + const Handle& getHandle() const { return *handle; } + + operator bool() const { return handle.operator bool(); } + + void reset() { handle.reset(); } + +private: + std::shared_ptr handle; +}; + +template <> +inline Variable detail::getSlot(WrenVM* vm, const int idx) { + validate(vm, idx); + return Variable(std::make_shared(getSharedVm(vm), wrenGetSlotHandle(vm, idx))); +} + +template <> +inline bool detail::is(WrenVM* vm, const int idx) { + return wrenGetSlotType(vm, idx) == WREN_TYPE_UNKNOWN; +} + +template <> +inline void detail::PushHelper::f(WrenVM* vm, int idx, const Variable& value) { + wrenSetSlotHandle(value.getHandle().getVm(), idx, value.getHandle().getHandle()); +} + +template <> +inline void detail::PushHelper::f(WrenVM* vm, int idx, Variable&& value) { + wrenSetSlotHandle(value.getHandle().getVm(), idx, value.getHandle().getHandle()); +} + +template <> +inline void detail::PushHelper::f(WrenVM* vm, int idx, const Variable value) { + wrenSetSlotHandle(value.getHandle().getVm(), idx, value.getHandle().getHandle()); +} + +template <> +inline void detail::PushHelper::f(WrenVM* vm, int idx, const Variable& value) { + wrenSetSlotHandle(value.getHandle().getVm(), idx, value.getHandle().getHandle()); +} + +template <> +inline void detail::PushHelper::f(WrenVM* vm, int idx, Variable& value) { + wrenSetSlotHandle(value.getHandle().getVm(), idx, value.getHandle().getHandle()); +} +} // namespace wrenbind17 diff --git a/be/src/thirdparty/wrenbind17/wrenbind17/vm.hpp b/be/src/thirdparty/wrenbind17/wrenbind17/vm.hpp new file mode 100644 index 00000000000..80d3770ccc5 --- /dev/null +++ b/be/src/thirdparty/wrenbind17/wrenbind17/vm.hpp @@ -0,0 +1,422 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "exception.hpp" +#include "map.hpp" +#include "module.hpp" +#include "variable.hpp" + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +namespace std { +template <> +struct hash> { + inline size_t operator()(const std::pair& v) const { + const std::hash hasher; + return hasher(v.first) ^ hasher(v.second); + } +}; +} // namespace std +#endif + +/** + * @ingroup wrenbind17 + */ +namespace wrenbind17 { +/** + * @ingroup wrenbind17 + */ +typedef std::function PrintFn; + +/** + * @ingroup wrenbind17 + */ +typedef std::function& paths, const std::string& name)> LoadFileFn; + +/** + * @ingroup wrenbind17 + * @brief Holds the entire Wren VM from which all of the magic happens + */ +class VM { +public: + /*! + * @brief The only constructor available + * @param paths The lookup paths used by the import loader function + * @param initHeap The size of the heap at the beginning + * @param minHeap The minimum size of the heap + * @param heapGrowth How the heap should grow + */ + inline explicit VM(std::vector paths = {"./"}, const size_t initHeap = 1024 * 1024, + const size_t minHeap = 1024 * 1024 * 10, const int heapGrowth = 50) + : data(std::make_unique()) { + data->paths = std::move(paths); + data->printFn = [](const char* text) -> void { std::cout << text; }; + data->loadFileFn = [](const std::vector& paths, const std::string& name) -> std::string { + for (const auto& path : paths) { + const auto test = path + "/" + std::string(name) + ".wren"; + + std::ifstream t(test); + if (!t) continue; + + std::string source((std::istreambuf_iterator(t)), std::istreambuf_iterator()); + return source; + } + + throw NotFound(); + }; + + wrenInitConfiguration(&data->config); + data->config.initialHeapSize = initHeap; + data->config.minHeapSize = minHeap; + data->config.heapGrowthPercent = heapGrowth; + data->config.userData = data.get(); +#if WREN_VERSION_NUMBER >= 4000 // >= 0.4.0 + data->config.reallocateFn = [](void* memory, size_t newSize, void* userData) -> void* { + return std::realloc(memory, newSize); + }; + data->config.loadModuleFn = [](WrenVM* vm, const char* name) -> WrenLoadModuleResult { + auto res = WrenLoadModuleResult(); + auto& self = *reinterpret_cast(wrenGetUserData(vm)); + + const auto mod = self.modules.find(name); + if (mod != self.modules.end()) { + auto source = mod->second.str(); + auto buffer = new char[source.size() + 1]; + std::memcpy(buffer, &source[0], source.size() + 1); + res.source = buffer; + res.onComplete = [](WrenVM* vm, const char* name, struct WrenLoadModuleResult result) { + delete[] result.source; + }; + return res; + } + + try { + auto source = self.loadFileFn(self.paths, std::string(name)); + auto buffer = new char[source.size() + 1]; + std::memcpy(buffer, &source[0], source.size() + 1); + res.source = buffer; + res.onComplete = [](WrenVM* vm, const char* name, struct WrenLoadModuleResult result) { + delete[] result.source; + }; + } catch (std::exception& e) { + (void)e; + } + return res; + }; +#else // < 0.4.0 + data->config.reallocateFn = std::realloc; + data->config.loadModuleFn = [](WrenVM* vm, const char* name) -> char* { + auto& self = *reinterpret_cast(wrenGetUserData(vm)); + + const auto mod = self.modules.find(name); + if (mod != self.modules.end()) { + auto source = mod->second.str(); + auto buffer = new char[source.size() + 1]; + std::memcpy(buffer, &source[0], source.size() + 1); + return buffer; + } + + try { + auto source = self.loadFileFn(self.paths, std::string(name)); + auto buffer = new char[source.size() + 1]; + std::memcpy(buffer, &source[0], source.size() + 1); + return buffer; + } catch (std::exception& e) { + (void)e; + return nullptr; + } + }; +#endif // WREN_VERSION_NUMBER >= 4000 + data->config.bindForeignMethodFn = [](WrenVM* vm, const char* module, const char* className, + const bool isStatic, const char* signature) -> WrenForeignMethodFn { + auto& self = *reinterpret_cast(wrenGetUserData(vm)); + try { + auto& found = self.modules.at(module); + auto& klass = found.findKlass(className); + return klass.findSignature(signature, isStatic); + } catch (...) { + std::cerr << "Wren foreign method " << signature << " not found in C++" << std::endl; + std::abort(); + return nullptr; + } + }; + data->config.bindForeignClassFn = [](WrenVM* vm, const char* module, + const char* className) -> WrenForeignClassMethods { + auto& self = *reinterpret_cast(wrenGetUserData(vm)); + try { + auto& found = self.modules.at(module); + auto& klass = found.findKlass(className); + return klass.getAllocators(); + } catch (...) { + exceptionHandler(vm, std::current_exception()); + return WrenForeignClassMethods{nullptr, nullptr}; + } + }; + data->config.writeFn = [](WrenVM* vm, const char* text) { + auto& self = *reinterpret_cast(wrenGetUserData(vm)); + self.printFn(text); + }; + data->config.errorFn = [](WrenVM* vm, WrenErrorType type, const char* module, const int line, + const char* message) { + auto& self = *reinterpret_cast(wrenGetUserData(vm)); + std::stringstream ss; + switch (type) { + case WREN_ERROR_COMPILE: + ss << "Compile error: " << message << " at " << module << ":" << line << "\n"; + break; + case WREN_ERROR_RUNTIME: + if (!self.nextError.empty()) { + ss << "Runtime error: " << self.nextError << "\n"; + self.nextError.clear(); + } else { + ss << "Runtime error: " << message << "\n"; + } + break; + case WREN_ERROR_STACK_TRACE: + ss << " at: " << module << ":" << line << "\n"; + break; + default: + break; + } + self.lastError += ss.str(); + }; + + data->vm = std::shared_ptr(wrenNewVM(&data->config), [](WrenVM* ptr) { wrenFreeVM(ptr); }); + } + + inline VM(const VM& other) = delete; + + inline VM(VM&& other) noexcept { swap(other); } + + inline ~VM() = default; + + inline VM& operator=(const VM& other) = delete; + + inline VM& operator=(VM&& other) noexcept { + if (this != &other) { + swap(other); + } + return *this; + } + + inline void swap(VM& other) noexcept { std::swap(data, other.data); } + + /*! + * @brief Runs a Wren source code by passing it as a string + * @param name The module name to assign this code into, this module name + * can be then used to import this code in some other place + * @param code Your raw multiline Wren code + * @throws CompileError if the compilation has failed + */ + inline void runFromSource(const std::string& name, const std::string& code) { + const auto result = wrenInterpret(data->vm.get(), name.c_str(), code.c_str()); + if (result != WREN_RESULT_SUCCESS) { + throw CompileError(getLastError()); + } + return; + } + + /*! + * @brief Runs a Wren source code directly from a file + * @param name The module name to assign this code into, this module name + * can be then used to import this code in some other place + * @param path The path to the file + * @throws Exception if the file has not been found or the file cannot be read + * @throws CompileError if the compilation has failed + */ + inline void runFromFile(const std::string& name, const std::string& path) { + std::ifstream t(path); + if (!t) throw Exception("Compile error: Failed to open source file"); + std::string str((std::istreambuf_iterator(t)), std::istreambuf_iterator()); + runFromSource(name, str); + } + + /*! + * @brief Runs a Wren source code by passing it as a string + * @see setLoadFileFunc + * @param name The module name to load, this will use the loader function to + * load the file from + * @throws CompileError if the compilation has failed + */ + inline void runFromModule(const std::string& name) { + const auto source = data->loadFileFn(data->paths, name); + runFromSource(name, source); + } + + /*! + * @brief Looks up a variable from a module + * @param module The name of the module to look for the variable in + * @param name The name of the variable or a class itself that must start + * with a capital letter + * @throws NotFound if the variable has not been found + */ + inline Variable find(const std::string& module, const std::string& name) { + wrenEnsureSlots(data->vm.get(), 1); + wrenGetVariable(data->vm.get(), module.c_str(), name.c_str(), 0); + auto* handle = wrenGetSlotHandle(data->vm.get(), 0); + if (!handle) throw NotFound(); + return Variable(std::make_shared(data->vm, handle)); + } + + /*! + * @brief Creates a new custom module + * @note Calling this function multiple times with the same name + * does not create a new module, but instead it returns the same module. + */ + inline ForeignModule& module(const std::string& name) { + auto it = data->modules.find(name); + if (it == data->modules.end()) { + it = data->modules.insert(std::make_pair(name, ForeignModule(name, data->vm.get()))).first; + } + return it->second; + } + + inline void addClassType(const std::string& module, const std::string& name, const size_t hash) { + data->addClassType(module, name, hash); + } + + inline void getClassType(std::string& module, std::string& name, const size_t hash) { + data->getClassType(module, name, hash); + } + + inline bool isClassRegistered(const size_t hash) const { return data->isClassRegistered(hash); } + + inline void addClassCast(std::shared_ptr convertor, const size_t hash, + const size_t other) { + data->addClassCast(std::move(convertor), hash, other); + } + + inline detail::ForeignPtrConvertor* getClassCast(const size_t hash, const size_t other) { + return data->getClassCast(hash, other); + } + + inline std::string getLastError() { return data->getLastError(); } + + inline void setNextError(std::string str) { data->setNextError(std::move(str)); } + + /*! + * @brief Set a custom print function that is used by the System.print() + * @see PrintFn + */ + inline void setPrintFunc(const PrintFn& fn) { data->printFn = fn; } + + /*! + * @brief Set a custom loader function for imports + * @see LoadFileFn + * @details This must be a function that accepts a std::vector of strings + * (which are the lookup paths from the constructor) and the name of the import + * as the second parameter. You must return the source code from this custom function. + * If you want to cancel the import, simply throw an exception. + */ + inline void setLoadFileFunc(const LoadFileFn& fn) { data->loadFileFn = fn; } + + /*! + * @brief Runs the garbage collector + */ + inline void gc() { wrenCollectGarbage(data->vm.get()); } + + class Data { + public: + std::shared_ptr vm; + WrenConfiguration config; + std::vector paths; + std::unordered_map modules; + std::unordered_map classToModule; + std::unordered_map classToName; + std::unordered_map, std::shared_ptr> classCasting; + std::string lastError; + std::string nextError; + PrintFn printFn; + LoadFileFn loadFileFn; + + inline void addClassType(const std::string& module, const std::string& name, const size_t hash) { + classToModule.insert(std::make_pair(hash, module)); + classToName.insert(std::make_pair(hash, name)); + } + + inline void getClassType(std::string& module, std::string& name, const size_t hash) { + module = classToModule.at(hash); + name = classToName.at(hash); + } + + inline bool isClassRegistered(const size_t hash) const { + return classToModule.find(hash) != classToModule.end(); + } + + inline void addClassCast(std::shared_ptr convertor, const size_t hash, + const size_t other) { + classCasting.insert(std::make_pair(std::make_pair(hash, other), std::move(convertor))); + } + + inline detail::ForeignPtrConvertor* getClassCast(const size_t hash, const size_t other) { + return classCasting.at(std::pair(hash, other)).get(); + } + + inline std::string getLastError() { + std::string str; + std::swap(str, lastError); + return str; + } + + inline void setNextError(std::string str) { nextError = std::move(str); } + }; + +private: + std::unique_ptr data; +}; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +inline std::shared_ptr getSharedVm(WrenVM* vm) { + assert(vm); + auto self = reinterpret_cast(wrenGetUserData(vm)); + assert(self->vm); + return self->vm; +} +inline void addClassType(WrenVM* vm, const std::string& module, const std::string& name, const size_t hash) { + assert(vm); + auto self = reinterpret_cast(wrenGetUserData(vm)); + self->addClassType(module, name, hash); +} +inline void getClassType(WrenVM* vm, std::string& module, std::string& name, const size_t hash) { + assert(vm); + auto self = reinterpret_cast(wrenGetUserData(vm)); + self->getClassType(module, name, hash); +} +inline bool isClassRegistered(WrenVM* vm, const size_t hash) { + assert(vm); + auto self = reinterpret_cast(wrenGetUserData(vm)); + return self->isClassRegistered(hash); +} +inline void addClassCast(WrenVM* vm, std::shared_ptr convertor, const size_t hash, + const size_t other) { + assert(vm); + auto self = reinterpret_cast(wrenGetUserData(vm)); + self->addClassCast(std::move(convertor), hash, other); +} +inline detail::ForeignPtrConvertor* getClassCast(WrenVM* vm, const size_t hash, const size_t other) { + assert(vm); + auto self = reinterpret_cast(wrenGetUserData(vm)); + return self->getClassCast(hash, other); +} +inline std::string getLastError(WrenVM* vm) { + assert(vm); + auto self = reinterpret_cast(wrenGetUserData(vm)); + return self->getLastError(); +} +inline void setNextError(WrenVM* vm, std::string str) { + assert(vm); + auto self = reinterpret_cast(wrenGetUserData(vm)); + self->setNextError(std::move(str)); +} +#endif +} // namespace wrenbind17 diff --git a/be/src/thirdparty/wrenbind17/wrenbind17/wrenbind17.hpp b/be/src/thirdparty/wrenbind17/wrenbind17/wrenbind17.hpp new file mode 100644 index 00000000000..c18503a5da9 --- /dev/null +++ b/be/src/thirdparty/wrenbind17/wrenbind17/wrenbind17.hpp @@ -0,0 +1,16 @@ +#pragma once + +/** + * @defgroup wrenbind17 + * @brief Wren lang binding library for C++17 + */ + +#include "std.hpp" +#include "stddeque.hpp" +#include "stdlist.hpp" +#include "stdmap.hpp" +#include "stdoptional.hpp" +#include "stdset.hpp" +#include "stdvariant.hpp" +#include "stdvector.hpp" +#include "vm.hpp" diff --git a/be/test/runtime/command_executor_test.cpp b/be/test/runtime/command_executor_test.cpp index e76b4f2bd0a..3d3cee91d55 100644 --- a/be/test/runtime/command_executor_test.cpp +++ b/be/test/runtime/command_executor_test.cpp @@ -21,7 +21,8 @@ namespace starrocks { TEST(CommandExecutorTest, not_support) { - EXPECT_TRUE(execute_command("set_config_11", "{}").is_not_supported()); + std::string result; + EXPECT_TRUE(execute_command("set_config_11", "{}", &result).is_not_supported()); } } // namespace starrocks diff --git a/fe/fe-core/src/main/java/com/starrocks/qe/ExecuteScriptExecutor.java b/fe/fe-core/src/main/java/com/starrocks/qe/ExecuteScriptExecutor.java new file mode 100644 index 00000000000..044c3c629f5 --- /dev/null +++ b/fe/fe-core/src/main/java/com/starrocks/qe/ExecuteScriptExecutor.java @@ -0,0 +1,62 @@ +// Copyright 2021-present StarRocks, Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.starrocks.qe; + +import com.starrocks.common.UserException; +import com.starrocks.proto.ExecuteCommandRequestPB; +import com.starrocks.proto.ExecuteCommandResultPB; +import com.starrocks.rpc.DataNodeServiceClient; +import com.starrocks.server.GlobalStateMgr; +import com.starrocks.sql.ast.ExecuteScriptStmt; +import com.starrocks.system.DataNode; +import com.starrocks.thrift.TNetworkAddress; +import org.apache.commons.lang.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +public class ExecuteScriptExecutor { + private static final Logger LOG = LogManager.getLogger(ExecuteScriptExecutor.class); + + public static void execute(ExecuteScriptStmt stmt, ConnectContext ctx) throws UserException { + DataNode be = GlobalStateMgr.getCurrentSystemInfo().getBackend(stmt.getBeId()); + TNetworkAddress address = new TNetworkAddress(be.getHost(), be.getBrpcPort()); + ExecuteCommandRequestPB request = new ExecuteCommandRequestPB(); + request.command = "execute_script"; + request.params = stmt.getScript(); + try { + Future future = DataNodeServiceClient.getInstance().executeCommand(address, request); + ExecuteCommandResultPB result = future.get(stmt.getTimeoutSec(), TimeUnit.SECONDS); + if (result.status.statusCode != 0) { + LOG.warn("execute script error BE: {} script:{} result: {}", stmt.getBeId(), + StringUtils.abbreviate(stmt.getScript(), 1000), + result.status.errorMsgs); + throw new UserException(result.status.toString()); + } else { + LOG.info("execute script ok BE: {} script:{} result: {}", stmt.getBeId(), + StringUtils.abbreviate(stmt.getScript(), 1000), StringUtils.abbreviate(result.result, 1000)); + ctx.getState().setOk(0, 0, result.result); + } + } catch (InterruptedException ie) { + LOG.warn("got interrupted exception when sending proxy request to " + address); + Thread.currentThread().interrupt(); + throw new UserException("got interrupted exception when sending proxy request to " + address); + } catch (Exception e) { + throw new UserException("executeCommand RPC failed BE:" + address + " err " + e.getMessage()); + } + } +} diff --git a/fe/fe-core/src/main/java/com/starrocks/qe/StmtExecutor.java b/fe/fe-core/src/main/java/com/starrocks/qe/StmtExecutor.java index 4a017a8d273..529d30b325a 100644 --- a/fe/fe-core/src/main/java/com/starrocks/qe/StmtExecutor.java +++ b/fe/fe-core/src/main/java/com/starrocks/qe/StmtExecutor.java @@ -112,6 +112,7 @@ import com.starrocks.sql.ast.DmlStmt; import com.starrocks.sql.ast.DropHistogramStmt; import com.starrocks.sql.ast.DropStatsStmt; import com.starrocks.sql.ast.ExecuteAsStmt; +import com.starrocks.sql.ast.ExecuteScriptStmt; import com.starrocks.sql.ast.ExportStmt; import com.starrocks.sql.ast.InsertStmt; import com.starrocks.sql.ast.KillAnalyzeStmt; @@ -541,6 +542,8 @@ public class StmtExecutor { handleDelSqlBlackListStmt(); } else if (parsedStmt instanceof ExecuteAsStmt) { handleExecAsStmt(); + } else if (parsedStmt instanceof ExecuteScriptStmt) { + handleExecScriptStmt(); } else if (parsedStmt instanceof SetRoleStmt) { handleSetRole(); } else if (parsedStmt instanceof SetDefaultRoleStmt) { @@ -988,6 +991,10 @@ public class StmtExecutor { ExecuteAsExecutor.execute((ExecuteAsStmt) parsedStmt, context); } + private void handleExecScriptStmt() throws PrivilegeException, UserException { + ExecuteScriptExecutor.execute((ExecuteScriptStmt) parsedStmt, context); + } + private void handleSetRole() throws PrivilegeException, UserException { SetRoleExecutor.execute((SetRoleStmt) parsedStmt, context); } diff --git a/fe/fe-core/src/main/java/com/starrocks/rpc/DataNodeServiceClient.java b/fe/fe-core/src/main/java/com/starrocks/rpc/DataNodeServiceClient.java index fa3ee6299f4..98e3adc7420 100644 --- a/fe/fe-core/src/main/java/com/starrocks/rpc/DataNodeServiceClient.java +++ b/fe/fe-core/src/main/java/com/starrocks/rpc/DataNodeServiceClient.java @@ -36,6 +36,8 @@ package com.starrocks.rpc; import com.google.common.base.Preconditions; import com.starrocks.common.Config; +import com.starrocks.proto.ExecuteCommandRequestPB; +import com.starrocks.proto.ExecuteCommandResultPB; import com.starrocks.proto.PCancelPlanFragmentRequest; import com.starrocks.proto.PCancelPlanFragmentResult; import com.starrocks.proto.PCollectQueryStatisticsResult; @@ -265,6 +267,18 @@ public class DataNodeServiceClient { return resultFuture; } + public Future executeCommand(TNetworkAddress address, ExecuteCommandRequestPB request) + throws RpcException { + try { + final PBackendService service = BrpcProxy.getBackendService(address); + return service.executeCommandAsync(request); + } catch (Throwable e) { + LOG.warn("execute command exception, address={}:{} command:{}", + address.getHostname(), address.getPort(), request.command, e); + throw new RpcException(address.hostname, e.getMessage()); + } + } + private static class SingletonHolder { private static final DataNodeServiceClient INSTANCE = new DataNodeServiceClient(); } diff --git a/fe/fe-core/src/main/java/com/starrocks/rpc/PBackendService.java b/fe/fe-core/src/main/java/com/starrocks/rpc/PBackendService.java index 737760bff04..b5520c13804 100644 --- a/fe/fe-core/src/main/java/com/starrocks/rpc/PBackendService.java +++ b/fe/fe-core/src/main/java/com/starrocks/rpc/PBackendService.java @@ -68,8 +68,7 @@ public interface PBackendService { attachmentHandler = ThriftClientAttachmentHandler.class) Future submitMVMaintenanceTaskAsync(PMVMaintenanceTaskRequest request); - @ProtobufRPC(serviceName = "PBackendService", methodName = "execute_command", onceTalkTimeout = 60000, - attachmentHandler = ThriftClientAttachmentHandler.class) + @ProtobufRPC(serviceName = "PBackendService", methodName = "execute_command", onceTalkTimeout = 60000) Future executeCommandAsync(ExecuteCommandRequestPB request); } diff --git a/fe/fe-core/src/main/java/com/starrocks/sql/analyzer/Analyzer.java b/fe/fe-core/src/main/java/com/starrocks/sql/analyzer/Analyzer.java index dc8031dade1..7a9fcc8d2e5 100644 --- a/fe/fe-core/src/main/java/com/starrocks/sql/analyzer/Analyzer.java +++ b/fe/fe-core/src/main/java/com/starrocks/sql/analyzer/Analyzer.java @@ -571,7 +571,6 @@ public class Analyzer { return null; } - // ------------------------------------------- Cluster Management Statement ---------------------------------------- @Override diff --git a/fe/fe-core/src/main/java/com/starrocks/sql/analyzer/PrivilegeChecker.java b/fe/fe-core/src/main/java/com/starrocks/sql/analyzer/PrivilegeChecker.java index 69ca1baf465..d53684346f1 100644 --- a/fe/fe-core/src/main/java/com/starrocks/sql/analyzer/PrivilegeChecker.java +++ b/fe/fe-core/src/main/java/com/starrocks/sql/analyzer/PrivilegeChecker.java @@ -91,6 +91,7 @@ import com.starrocks.sql.ast.DropRoleStmt; import com.starrocks.sql.ast.DropTableStmt; import com.starrocks.sql.ast.DropUserStmt; import com.starrocks.sql.ast.ExecuteAsStmt; +import com.starrocks.sql.ast.ExecuteScriptStmt; import com.starrocks.sql.ast.ExportStmt; import com.starrocks.sql.ast.InsertStmt; import com.starrocks.sql.ast.InstallPluginStmt; @@ -772,6 +773,15 @@ public class PrivilegeChecker { return null; } + @Override + public Void visitExecuteScriptStatement(ExecuteScriptStmt statement, ConnectContext session) { + if (!GlobalStateMgr.getCurrentState().getAuth() + .checkGlobalPriv(session, PrivPredicate.ADMIN)) { + ErrorReport.reportSemanticException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "EXECUTE SCRIPT"); + } + return null; + } + @Override public Void visitShowMaterializedViewStatement(ShowMaterializedViewsStmt statement, ConnectContext session) { String db = statement.getDb(); diff --git a/fe/fe-core/src/main/java/com/starrocks/sql/analyzer/PrivilegeCheckerV2.java b/fe/fe-core/src/main/java/com/starrocks/sql/analyzer/PrivilegeCheckerV2.java index e1de9ff077e..4be1234aad1 100644 --- a/fe/fe-core/src/main/java/com/starrocks/sql/analyzer/PrivilegeCheckerV2.java +++ b/fe/fe-core/src/main/java/com/starrocks/sql/analyzer/PrivilegeCheckerV2.java @@ -107,6 +107,7 @@ import com.starrocks.sql.ast.DropStatsStmt; import com.starrocks.sql.ast.DropTableStmt; import com.starrocks.sql.ast.DropUserStmt; import com.starrocks.sql.ast.ExecuteAsStmt; +import com.starrocks.sql.ast.ExecuteScriptStmt; import com.starrocks.sql.ast.ExportStmt; import com.starrocks.sql.ast.GrantRoleStmt; import com.starrocks.sql.ast.InsertStmt; @@ -1022,6 +1023,12 @@ public class PrivilegeCheckerV2 { return null; } + @Override + public Void visitExecuteScriptStatement(ExecuteScriptStmt statement, ConnectContext context) { + checkStmtOperatePrivilege(context); + return null; + } + @Override public Void visitCreateRoleStatement(CreateRoleStmt statement, ConnectContext context) { if (!PrivilegeActions.checkSystemAction(context, PrivilegeType.GRANT)) { diff --git a/fe/fe-core/src/main/java/com/starrocks/sql/ast/AstVisitor.java b/fe/fe-core/src/main/java/com/starrocks/sql/ast/AstVisitor.java index 5dc4aa48d66..5589e880c82 100644 --- a/fe/fe-core/src/main/java/com/starrocks/sql/ast/AstVisitor.java +++ b/fe/fe-core/src/main/java/com/starrocks/sql/ast/AstVisitor.java @@ -361,7 +361,6 @@ public abstract class AstVisitor { return visitShowStatement(statement, context); } - // ------------------------------------------- Admin Statement ----------------------------------------------------- public R visitAdminSetConfigStatement(AdminSetConfigStmt statement, C context) { @@ -692,6 +691,10 @@ public abstract class AstVisitor { return visitStatement(statement, context); } + public R visitExecuteScriptStatement(ExecuteScriptStmt statement, C context) { + return visitStatement(statement, context); + } + // --------------------------------------- Export Statement -------------------------------------------------------- public R visitExportStatement(ExportStmt statement, C context) { diff --git a/fe/fe-core/src/main/java/com/starrocks/sql/ast/ExecuteScriptStmt.java b/fe/fe-core/src/main/java/com/starrocks/sql/ast/ExecuteScriptStmt.java new file mode 100644 index 00000000000..0b4691f935f --- /dev/null +++ b/fe/fe-core/src/main/java/com/starrocks/sql/ast/ExecuteScriptStmt.java @@ -0,0 +1,70 @@ +// Copyright 2021-present StarRocks, Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.starrocks.sql.ast; + +import com.starrocks.analysis.RedirectStatus; +import com.starrocks.sql.parser.NodePosition; + +// EXECUTE ON