From 61166994698a9764ccb19823d6708288f0e79efc Mon Sep 17 00:00:00 2001 From: slavek-kucera <53339291+slavek-kucera@users.noreply.github.com> Date: Mon, 19 Dec 2022 11:28:48 +0100 Subject: [PATCH] feat: Support for the SYSCLOCK system variable --- clients/vscode-hlasmplugin/CHANGELOG.md | 1 + language_server/src/logger.cpp | 30 +--- parser_library/src/context/hlasm_context.cpp | 53 +++---- .../instruction_sets/ca_processor.cpp | 10 +- .../test/context/system_variable_test.cpp | 50 ++++++ .../test/debugging/debugger_test.cpp | 2 + utils/include/utils/CMakeLists.txt | 1 + utils/include/utils/time.h | 99 ++++++++++++ utils/src/CMakeLists.txt | 1 + utils/src/time.cpp | 144 ++++++++++++++++++ utils/test/CMakeLists.txt | 1 + utils/test/time_test.cpp | 31 ++++ 12 files changed, 368 insertions(+), 55 deletions(-) create mode 100644 utils/include/utils/time.h create mode 100644 utils/src/time.cpp create mode 100644 utils/test/time_test.cpp diff --git a/clients/vscode-hlasmplugin/CHANGELOG.md b/clients/vscode-hlasmplugin/CHANGELOG.md index d8a8fd48a..4e393455e 100644 --- a/clients/vscode-hlasmplugin/CHANGELOG.md +++ b/clients/vscode-hlasmplugin/CHANGELOG.md @@ -8,6 +8,7 @@ - Quick fixes for typos in instruction and macro names added to the code actions - Endevor and CICS preprocessor statements highlighting and parsing - Instruction suggestions are included in the completion list +- Support for the SYSCLOCK system variable #### Fixed - LSP requests on virtual files are evaluated in the correct workspace context diff --git a/language_server/src/logger.cpp b/language_server/src/logger.cpp index 5e379209f..dc29ec462 100644 --- a/language_server/src/logger.cpp +++ b/language_server/src/logger.cpp @@ -12,36 +12,22 @@ * Broadcom, Inc. - initial API and implementation */ +#include "logger.h" -#include -#define __STDC_WANT_LIB_EXT1__ 1 #include -#include +#include -#include "logger.h" +#include "utils/time.h" using namespace hlasm_plugin::language_server; std::string current_time() { - std::string curr_time; - // Current date/time based on current time - time_t now = time(0); - // Convert current time to string - -#if __STDC_LIB_EXT1__ || _MSVC_LANG - char cstr[50]; - ctime_s(cstr, sizeof cstr, &now); - curr_time.assign(cstr); -#else - curr_time.assign(ctime(&now)); -#endif - - // Last charactor of currentTime is "\n", so remove it - if (!curr_time.empty()) - curr_time.pop_back(); - - return curr_time; + auto t = hlasm_plugin::utils::timestamp::now(); + if (!t.has_value()) + return ""; + else + return t->to_string(); } void logger::log(std::string_view data) diff --git a/parser_library/src/context/hlasm_context.cpp b/parser_library/src/context/hlasm_context.cpp index 1dbeefc3e..368ad82a8 100644 --- a/parser_library/src/context/hlasm_context.cpp +++ b/parser_library/src/context/hlasm_context.cpp @@ -25,6 +25,7 @@ #include "lexing/lexer.h" #include "lexing/tools.h" #include "using.h" +#include "utils/time.h" namespace hlasm_plugin::parser_library::context { @@ -129,7 +130,7 @@ macro_data_ptr create_macro_data(std::vector value) macro_data_ptr create_macro_data(std::unique_ptr value) { return value; } -template +template std::pair create_system_variable(id_index id, DATA mac_data, bool is_global) { return { id, std::make_shared(id, create_macro_data(std::move(mac_data)), is_global) }; @@ -143,15 +144,13 @@ void hlasm_context::add_system_vars_to_scope(code_scope& scope) { auto sect_name = ord_ctx.current_section() ? ord_ctx.current_section()->name : id_index(); - scope.system_variables.insert( - create_system_variable(id_index("SYSECT"), sect_name.to_string(), false)); + scope.system_variables.insert(create_system_variable(id_index("SYSECT"), sect_name.to_string(), false)); } { std::string value = left_pad(std::to_string(SYSNDX_), 4, '0'); - scope.system_variables.insert( - create_system_variable(id_index("SYSNDX"), std::move(value), false)); + scope.system_variables.insert(create_system_variable(id_index("SYSNDX"), std::move(value), false)); } { @@ -178,8 +177,7 @@ void hlasm_context::add_system_vars_to_scope(code_scope& scope) } } - scope.system_variables.insert( - create_system_variable(id_index("SYSSTYP"), std::move(value), false)); + scope.system_variables.insert(create_system_variable(id_index("SYSSTYP"), std::move(value), false)); } { @@ -191,14 +189,13 @@ void hlasm_context::add_system_vars_to_scope(code_scope& scope) } scope.system_variables.insert( - create_system_variable(id_index("SYSLOC"), std::move(location_counter_name), false)); + create_system_variable(id_index("SYSLOC"), std::move(location_counter_name), false)); } { std::string value = std::to_string(scope_stack_.size() - 1); - scope.system_variables.insert( - create_system_variable(id_index("SYSNEST"), std::move(value), false)); + scope.system_variables.insert(create_system_variable(id_index("SYSNEST"), std::move(value), false)); } { @@ -217,13 +214,18 @@ void hlasm_context::add_system_vars_to_scope(code_scope& scope) } { - scope.system_variables.insert( - create_system_variable(id_index("SYSIN_DSN"), asm_options_.sysin_dsn, false)); + scope.system_variables.insert(create_system_variable(id_index("SYSIN_DSN"), asm_options_.sysin_dsn, false)); } { scope.system_variables.insert( - create_system_variable(id_index("SYSIN_MEMBER"), asm_options_.sysin_member, false)); + create_system_variable(id_index("SYSIN_MEMBER"), asm_options_.sysin_member, false)); + } + + { + scope.system_variables.insert(create_system_variable(id_index("SYSCLOCK"), + utils::timestamp::now().value_or(utils::timestamp(1900, 1, 1)).to_string(), + false)); } } } @@ -242,36 +244,35 @@ void hlasm_context::add_global_system_vars(code_scope& scope) if (!is_in_macro()) { { - auto tmp_now = std::time(0); - auto now = std::localtime(&tmp_now); + auto now = utils::timestamp::now().value_or(utils::timestamp(1900, 1, 1)); std::string datc_val; std::string date_val; datc_val.reserve(8); date_val.reserve(8); - auto year = std::to_string(now->tm_year + 1900); + auto year = std::to_string(now.year()); datc_val.append(year); - if (now->tm_mon + 1 < 10) + if (now.month() < 10) { datc_val.push_back('0'); date_val.push_back('0'); } - datc_val.append(std::to_string(now->tm_mon + 1)); + datc_val.append(std::to_string(now.month() + 1)); - date_val.append(std::to_string(now->tm_mon + 1)); + date_val.append(std::to_string(now.month() + 1)); date_val.push_back('/'); - if (now->tm_mday < 10) + if (now.day() < 10) { datc_val.push_back('0'); date_val.push_back('0'); } - datc_val.append(std::to_string(now->tm_mday)); + datc_val.append(std::to_string(now.day())); - date_val.append(std::to_string(now->tm_mday)); + date_val.append(std::to_string(now.day())); date_val.push_back('/'); date_val.append(year.c_str() + 2); @@ -287,13 +288,13 @@ void hlasm_context::add_global_system_vars(code_scope& scope) { std::string value; - if (now->tm_hour < 10) + if (now.hour() < 10) value.push_back('0'); - value.append(std::to_string(now->tm_hour)); + value.append(std::to_string(now.hour())); value.push_back(':'); - if (now->tm_min < 10) + if (now.minute() < 10) value.push_back('0'); - value.append(std::to_string(now->tm_min)); + value.append(std::to_string(now.minute())); globals_.insert(create_system_variable(id_index("SYSTIME"), std::move(value), true)); } diff --git a/parser_library/src/processing/instruction_sets/ca_processor.cpp b/parser_library/src/processing/instruction_sets/ca_processor.cpp index 7e7f3d39c..9da84d2d4 100644 --- a/parser_library/src/processing/instruction_sets/ca_processor.cpp +++ b/parser_library/src/processing/instruction_sets/ca_processor.cpp @@ -17,6 +17,7 @@ #include "expressions/conditional_assembly/terms/ca_symbol.h" #include "semantics/operand_impls.h" #include "semantics/range_provider.h" +#include "utils/time.h" using namespace hlasm_plugin::parser_library; using namespace processing; @@ -582,14 +583,9 @@ void ca_processor::process_AREAD(const semantics::complete_statement& stmt) constexpr const auto since_midnight = []() -> std::chrono::nanoseconds { using namespace std::chrono; - const auto now = system_clock::now(); + const auto now = utils::timestamp::now().value_or(utils::timestamp()); - const auto now_t = system_clock::to_time_t(now); - const auto tm = *std::localtime(&now_t); - - const auto subsecond = now - std::chrono::floor(now); - - return hours(tm.tm_hour) + minutes(tm.tm_min) + seconds(tm.tm_sec) + subsecond; + return hours(now.hour()) + minutes(now.minute()) + seconds(now.second()) + microseconds(now.microsecond()); }; std::string value_to_set; diff --git a/parser_library/test/context/system_variable_test.cpp b/parser_library/test/context/system_variable_test.cpp index f1d8e6117..d68121307 100644 --- a/parser_library/test/context/system_variable_test.cpp +++ b/parser_library/test/context/system_variable_test.cpp @@ -12,6 +12,8 @@ * Broadcom, Inc. - initial API and implementation */ +#include + #include "gtest/gtest.h" #include "../common_testing.h" @@ -450,3 +452,51 @@ TEST(system_variable, mnote_sys_variables) EXPECT_EQ(get_var_vector(a.hlasm_ctx(), "RES"), expected_res); EXPECT_EQ(get_var_vector(a.hlasm_ctx(), "HRES"), expected_hres); } + +TEST(system_variable, sysclock) +{ + std::string input = R"( + MACRO + MAC2 + GBLC &VAL3 +&VAL3 SETC '&SYSCLOCK' + MEND +* + MACRO + MAC + GBLC &VAR,&VAL1,&VAL2 +&T1 SETC T'&SYSCLOCK +&K1 SETA K'&SYSCLOCK +&VAR SETC '&T1 &K1' +&VAL1 SETC '&SYSCLOCK' +&I SETA 1000 +.LOOP ANOP , +&I SETA &I-1 + AIF (&I GT 0).LOOP + MAC2 +&VAL2 SETC '&SYSCLOCK' + MEND +* + GBLC &VAR,&VAL1,&VAL2,&VAL3 + MAC + END +)"; + + analyzer a(input); + a.analyze(); + a.collect_diags(); + EXPECT_TRUE(a.diags().empty()); + + EXPECT_EQ(get_var_value(a.hlasm_ctx(), "VAR"), "U 26"); + + auto val1 = get_var_value(a.hlasm_ctx(), "VAL1"); + auto val2 = get_var_value(a.hlasm_ctx(), "VAL2"); + auto val3 = get_var_value(a.hlasm_ctx(), "VAL3"); + + ASSERT_TRUE(val1 && val2 && val3); + + EXPECT_EQ(val1, val2); + EXPECT_NE(val1, val3); + + EXPECT_TRUE(std::regex_match(val1.value(), std::regex(R"(\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d\d\d\d)"))); +} diff --git a/parser_library/test/debugging/debugger_test.cpp b/parser_library/test/debugging/debugger_test.cpp index 75a70d770..f2f499b94 100644 --- a/parser_library/test/debugging/debugger_test.cpp +++ b/parser_library/test/debugging/debugger_test.cpp @@ -260,6 +260,7 @@ struct frame_vars_ignore_sys_vars : public frame_vars this->locals["&SYSMAC"]; this->locals["&SYSIN_DSN"]; this->locals["&SYSIN_MEMBER"]; + this->locals["&SYSCLOCK"]; } }; @@ -516,6 +517,7 @@ TEST(debugger, test) { "&SYSIN_DSN", "" }, { "&SYSIN_MEMBER", "" }, { "&VAR", "13" }, + { "&SYSCLOCK", test_var_value() }, }, {} // empty ord symbols )); diff --git a/utils/include/utils/CMakeLists.txt b/utils/include/utils/CMakeLists.txt index 3b7867063..94d5ebaea 100644 --- a/utils/include/utils/CMakeLists.txt +++ b/utils/include/utils/CMakeLists.txt @@ -24,6 +24,7 @@ target_sources(hlasm_utils PUBLIC resource_location.h similar.h string_operations.h + time.h truth_table.h unicode_text.h ) diff --git a/utils/include/utils/time.h b/utils/include/utils/time.h new file mode 100644 index 000000000..7feceafa1 --- /dev/null +++ b/utils/include/utils/time.h @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2022 Broadcom. + * The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Broadcom, Inc. - initial API and implementation + */ + +#ifndef HLASMPLUGIN_UTILS_TIME_H +#define HLASMPLUGIN_UTILS_TIME_H + +#include +#include +#include + +namespace hlasm_plugin::utils { + +class timestamp +{ + unsigned long long m_year : 18 = 0; + unsigned long long m_month : 4 = 0; + unsigned long long m_day : 5 = 0; + unsigned long long m_hour : 5 = 0; + unsigned long long m_minute : 6 = 0; + unsigned long long m_second : 6 = 0; + unsigned long long m_microsecond : 20 = 0; + +#ifndef _MSC_VER + unsigned long long as_ull() const noexcept + { + auto v = (unsigned long long)m_year; + v <<= 4; + v |= (unsigned long long)m_month; + v <<= 5; + v |= (unsigned long long)m_day; + v <<= 5; + v |= (unsigned long long)m_hour; + v <<= 6; + v |= (unsigned long long)m_minute; + v <<= 6; + v |= (unsigned long long)m_second; + v <<= 20; + v |= (unsigned long long)m_microsecond; + + return v; + } +#endif + +public: + timestamp() = default; + timestamp(unsigned year, + unsigned month, + unsigned day, + unsigned hour, + unsigned minute, + unsigned second, + unsigned microsecond) noexcept + : m_year(year) + , m_month(month) + , m_day(day) + , m_hour(hour) + , m_minute(minute) + , m_second(second) + , m_microsecond(microsecond) + {} + timestamp(unsigned year, unsigned month, unsigned day) noexcept + : timestamp(year, month, day, 0, 0, 0, 0) + {} + unsigned year() const noexcept { return m_year; } + unsigned month() const noexcept { return m_month; } + unsigned day() const noexcept { return m_day; } + unsigned hour() const noexcept { return m_hour; } + unsigned minute() const noexcept { return m_minute; } + unsigned second() const noexcept { return m_second; } + unsigned microsecond() const noexcept { return m_microsecond; } + +#ifdef _MSC_VER + auto operator<=>(const timestamp&) const noexcept = default; +#else + // both gcc and clang have issues with this??? + auto operator<=>(const timestamp& o) const noexcept { return as_ull() <=> o.as_ull(); } + bool operator==(const timestamp& o) const noexcept { return as_ull() == o.as_ull(); } +#endif // _MSC_VER + + std::string to_string() const; + + static std::optional now(); +}; + + +} // namespace hlasm_plugin::utils + +#endif diff --git a/utils/src/CMakeLists.txt b/utils/src/CMakeLists.txt index 327cd1d32..1a246dc0f 100644 --- a/utils/src/CMakeLists.txt +++ b/utils/src/CMakeLists.txt @@ -19,6 +19,7 @@ target_sources(hlasm_utils PRIVATE resource_location.cpp string_operations.cpp unicode_text.cpp + time.cpp ) if(EMSCRIPTEN) diff --git a/utils/src/time.cpp b/utils/src/time.cpp new file mode 100644 index 000000000..521dcb7c5 --- /dev/null +++ b/utils/src/time.cpp @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2022 Broadcom. + * The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Broadcom, Inc. - initial API and implementation + */ + +#include "utils/time.h" + +#include +#ifdef __EMSCRIPTEN__ +# include + +# include +#else +# define __STDC_WANT_LIB_EXT1__ +# include +# include +#endif + +namespace { +// template has lower overload priority than the original function +struct tm* localtime_r(const auto* timer, struct tm* buf) +{ + if (auto ret = localtime(timer)) + { + *buf = *ret; + return buf; + } + return nullptr; +} +} // namespace + +namespace hlasm_plugin::utils { + +std::string timestamp::to_string() const +{ + constexpr auto padded_append = [](auto& where, const auto& what, size_t pad) { + auto s = std::to_string(what); + if (s.size() < pad) + where.append(pad - s.size(), '0'); + where.append(s); + }; + + std::string curr_time; + curr_time.reserve(std::string_view("yyyy-MM-dd HH:mm:ss.SSSSSS").size()); + + padded_append(curr_time, year(), 4); + curr_time.append(1, '-'); + padded_append(curr_time, month(), 2); + curr_time.append(1, '-'); + padded_append(curr_time, day(), 2); + curr_time.append(1, ' '); + padded_append(curr_time, hour(), 2); + curr_time.append(1, ':'); + padded_append(curr_time, minute(), 2); + curr_time.append(1, ':'); + padded_append(curr_time, second(), 2); + curr_time.append(1, '.'); + padded_append(curr_time, microsecond(), 6); + + return curr_time; +} + +std::optional timestamp::now() +{ +#ifdef __EMSCRIPTEN__ + // clang-format off + double compressed = EM_ASM_DOUBLE({ + try + { + const x = new Date(); + let r = x.getFullYear(); + r = r * 16 + x.getMonth(); + r = r * 32 + x.getDate(); + r = r * 32 + x.getHours(); + r = r * 64 + x.getMinutes(); + r = r * 64 + x.getSeconds(); + r = r * 1024 + x.getMilliseconds(); + + return r; + } + catch (e) + { + return -1; + } + }); + // clang-format on + if (compressed < 0) + return std::nullopt; + + constexpr auto shift_out = [](auto& value, int bits) { + auto result = value & (1 << bits) - 1; + value >>= bits; + return result; + }; + auto v = (unsigned long long)compressed; + + auto microseconds = 1000 * shift_out(v, 10); + auto second = shift_out(v, 6); + auto minute = shift_out(v, 6); + auto hour = shift_out(v, 5); + auto day = shift_out(v, 5); + auto month = shift_out(v, 4); + auto year = v; + + return timestamp(year, month, day, hour, minute, second, microseconds); +#else + using namespace std::chrono; + const auto now = system_clock::now(); + const auto now_t = system_clock::to_time_t(now); + + struct tm tm_buf; +# ifdef __STDC_LIB_EXT1__ + if (!localtime_s(&now_t, &tm_buf)) + return std::nullopt; +# elif defined _MSC_VER + if (localtime_s(&tm_buf, &now_t)) + return std::nullopt; +# else + if (!localtime_r(&now_t, &tm_buf)) + return std::nullopt; +# endif + + const auto subsecond = now - floor(now); + + return timestamp(tm_buf.tm_year + 1900, + tm_buf.tm_mon + 1, + tm_buf.tm_mday, + tm_buf.tm_hour, + tm_buf.tm_min, + tm_buf.tm_sec, + static_cast(std::chrono::nanoseconds(subsecond).count() / 1000)); +#endif +} + +} // namespace hlasm_plugin::utils diff --git a/utils/test/CMakeLists.txt b/utils/test/CMakeLists.txt index 42dbe98b9..6d845908a 100644 --- a/utils/test/CMakeLists.txt +++ b/utils/test/CMakeLists.txt @@ -20,6 +20,7 @@ target_sources(hlasm_utils_test PRIVATE platform_test.cpp resource_location_test.cpp unicode_text_test.cpp + time_test.cpp ) target_link_libraries(hlasm_utils_test hlasm_utils) diff --git a/utils/test/time_test.cpp b/utils/test/time_test.cpp new file mode 100644 index 000000000..964a44375 --- /dev/null +++ b/utils/test/time_test.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2022 Broadcom. + * The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Broadcom, Inc. - initial API and implementation + */ + +#include "gtest/gtest.h" + +#include "utils/time.h" + +using namespace hlasm_plugin::utils; + +TEST(timestamp, compare) { EXPECT_LT(timestamp(2000, 1, 2), timestamp(2000, 1, 2, 3, 4, 5, 6)); } + +TEST(timestamp, now) { EXPECT_GT(timestamp::now(), timestamp(2000, 1, 1)); } + +TEST(timestamp, components) +{ + auto t = timestamp::now().value(); + EXPECT_EQ(t, timestamp(t.year(), t.month(), t.day(), t.hour(), t.minute(), t.second(), t.microsecond())); +} + +TEST(timestamp, to_string) { EXPECT_EQ(timestamp(1, 2, 3, 4, 5, 6, 7).to_string(), "0001-02-03 04:05:06.000007"); }