mirror of
https://github.com/hyprwm/hyprutils.git
synced 2024-11-16 23:05:58 +01:00
utils: Initial Commit
This commit is contained in:
parent
cbc7c2df31
commit
bf73356d39
17 changed files with 1025 additions and 0 deletions
65
.clang-format
Normal file
65
.clang-format
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
---
|
||||||
|
Language: Cpp
|
||||||
|
BasedOnStyle: LLVM
|
||||||
|
|
||||||
|
AccessModifierOffset: -2
|
||||||
|
AlignAfterOpenBracket: Align
|
||||||
|
AlignConsecutiveMacros: true
|
||||||
|
AlignConsecutiveAssignments: true
|
||||||
|
AlignEscapedNewlines: Right
|
||||||
|
AlignOperands: false
|
||||||
|
AlignTrailingComments: true
|
||||||
|
AllowAllArgumentsOnNextLine: true
|
||||||
|
AllowAllConstructorInitializersOnNextLine: true
|
||||||
|
AllowAllParametersOfDeclarationOnNextLine: true
|
||||||
|
AllowShortBlocksOnASingleLine: true
|
||||||
|
AllowShortCaseLabelsOnASingleLine: true
|
||||||
|
AllowShortFunctionsOnASingleLine: Empty
|
||||||
|
AllowShortIfStatementsOnASingleLine: Never
|
||||||
|
AllowShortLambdasOnASingleLine: All
|
||||||
|
AllowShortLoopsOnASingleLine: false
|
||||||
|
AlwaysBreakAfterDefinitionReturnType: None
|
||||||
|
AlwaysBreakAfterReturnType: None
|
||||||
|
AlwaysBreakBeforeMultilineStrings: false
|
||||||
|
AlwaysBreakTemplateDeclarations: Yes
|
||||||
|
BreakBeforeBraces: Attach
|
||||||
|
BreakBeforeTernaryOperators: false
|
||||||
|
BreakConstructorInitializers: AfterColon
|
||||||
|
ColumnLimit: 180
|
||||||
|
CompactNamespaces: false
|
||||||
|
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||||
|
ExperimentalAutoDetectBinPacking: false
|
||||||
|
FixNamespaceComments: false
|
||||||
|
IncludeBlocks: Preserve
|
||||||
|
IndentCaseLabels: true
|
||||||
|
IndentWidth: 4
|
||||||
|
PointerAlignment: Left
|
||||||
|
ReflowComments: false
|
||||||
|
SortIncludes: false
|
||||||
|
SortUsingDeclarations: false
|
||||||
|
SpaceAfterCStyleCast: false
|
||||||
|
SpaceAfterLogicalNot: false
|
||||||
|
SpaceAfterTemplateKeyword: true
|
||||||
|
SpaceBeforeCtorInitializerColon: true
|
||||||
|
SpaceBeforeInheritanceColon: true
|
||||||
|
SpaceBeforeParens: ControlStatements
|
||||||
|
SpaceBeforeRangeBasedForLoopColon: true
|
||||||
|
SpaceInEmptyParentheses: false
|
||||||
|
SpacesBeforeTrailingComments: 1
|
||||||
|
SpacesInAngles: false
|
||||||
|
SpacesInCStyleCastParentheses: false
|
||||||
|
SpacesInContainerLiterals: false
|
||||||
|
SpacesInParentheses: false
|
||||||
|
SpacesInSquareBrackets: false
|
||||||
|
Standard: Auto
|
||||||
|
TabWidth: 4
|
||||||
|
UseTab: Never
|
||||||
|
|
||||||
|
AllowShortEnumsOnASingleLine: false
|
||||||
|
|
||||||
|
BraceWrapping:
|
||||||
|
AfterEnum: false
|
||||||
|
|
||||||
|
AlignConsecutiveDeclarations: AcrossEmptyLines
|
||||||
|
|
||||||
|
NamespaceIndentation: All
|
57
.github/workflows/arch.yml
vendored
Normal file
57
.github/workflows/arch.yml
vendored
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
name: Build & Test (Arch)
|
||||||
|
|
||||||
|
on: [push, pull_request, workflow_dispatch]
|
||||||
|
jobs:
|
||||||
|
gcc:
|
||||||
|
name: "Arch: Build and Test (gcc)"
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: archlinux
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository actions
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
sparse-checkout: .github/actions
|
||||||
|
|
||||||
|
- name: Get required pkgs
|
||||||
|
run: |
|
||||||
|
sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf
|
||||||
|
pacman --noconfirm --noprogressbar -Syyu
|
||||||
|
pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang libc++
|
||||||
|
|
||||||
|
- name: Build hyprutils with gcc
|
||||||
|
run: |
|
||||||
|
CC="/usr/bin/gcc" CXX="/usr/bin/g++" cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -S . -B ./build
|
||||||
|
CC="/usr/bin/gcc" CXX="/usr/bin/g++" cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`
|
||||||
|
cmake --install ./build
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: |
|
||||||
|
cd ./build && ctest --output-on-failure
|
||||||
|
|
||||||
|
clang:
|
||||||
|
name: "Arch: Build and Test (clang)"
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: archlinux
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository actions
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
sparse-checkout: .github/actions
|
||||||
|
|
||||||
|
- name: Get required pkgs
|
||||||
|
run: |
|
||||||
|
sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf
|
||||||
|
pacman --noconfirm --noprogressbar -Syyu
|
||||||
|
pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang libc++
|
||||||
|
|
||||||
|
- name: Build hyprutils with clang
|
||||||
|
run: |
|
||||||
|
CC="/usr/bin/clang" CXX="/usr/bin/clang++" cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -S . -B ./build
|
||||||
|
CC="/usr/bin/clang" CXX="/usr/bin/clang++" cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`
|
||||||
|
cmake --install ./build
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: |
|
||||||
|
cd ./build && ctest --output-on-failure
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -30,3 +30,7 @@
|
||||||
*.exe
|
*.exe
|
||||||
*.out
|
*.out
|
||||||
*.app
|
*.app
|
||||||
|
|
||||||
|
build/
|
||||||
|
.vscode/
|
||||||
|
.cache/
|
||||||
|
|
63
CMakeLists.txt
Normal file
63
CMakeLists.txt
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
cmake_minimum_required(VERSION 3.19)
|
||||||
|
|
||||||
|
set(HYPRUTILS_VERSION "0.1.0")
|
||||||
|
add_compile_definitions(HYPRUTILS_VERSION="${HYPRUTILS_VERSION}")
|
||||||
|
|
||||||
|
project(hyprutils
|
||||||
|
VERSION ${HYPRUTILS_VERSION}
|
||||||
|
DESCRIPTION "A library and toolkit for the Hyprland cursor format"
|
||||||
|
)
|
||||||
|
|
||||||
|
include(CTest)
|
||||||
|
include(GNUInstallDirs)
|
||||||
|
|
||||||
|
set(PREFIX ${CMAKE_INSTALL_PREFIX})
|
||||||
|
set(INCLUDE ${CMAKE_INSTALL_FULL_INCLUDEDIR})
|
||||||
|
set(LIBDIR ${CMAKE_INSTALL_FULL_LIBDIR})
|
||||||
|
|
||||||
|
configure_file(hyprutils.pc.in hyprutils.pc @ONLY)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 23)
|
||||||
|
|
||||||
|
if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)
|
||||||
|
message(STATUS "Configuring hyprutils in Debug")
|
||||||
|
add_compile_definitions(HYPRLAND_DEBUG)
|
||||||
|
else()
|
||||||
|
add_compile_options(-O3)
|
||||||
|
message(STATUS "Configuring hyprutils in Release")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp" "include/*.hpp")
|
||||||
|
|
||||||
|
add_library(hyprutils SHARED ${SRCFILES})
|
||||||
|
target_include_directories( hyprutils
|
||||||
|
PUBLIC "./include"
|
||||||
|
PRIVATE "./src"
|
||||||
|
)
|
||||||
|
set_target_properties(hyprutils PROPERTIES
|
||||||
|
VERSION ${hyprutils_VERSION}
|
||||||
|
SOVERSION 0
|
||||||
|
PUBLIC_HEADER include/hyprutils/hyprutils.hpp include/hyprutils/hyprutils.h include/hyprutils/shared.h
|
||||||
|
)
|
||||||
|
|
||||||
|
# tests
|
||||||
|
add_custom_target(tests)
|
||||||
|
|
||||||
|
add_executable(hyprutils_memory "tests/memory.cpp")
|
||||||
|
target_link_libraries(hyprutils_memory PRIVATE hyprutils)
|
||||||
|
add_test(NAME "Memory" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND hyprutils_memory "memory")
|
||||||
|
add_dependencies(tests hyprutils_memory)
|
||||||
|
|
||||||
|
add_executable(hyprutils_string "tests/string.cpp")
|
||||||
|
target_link_libraries(hyprutils_string PRIVATE hyprutils)
|
||||||
|
add_test(NAME "String" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND hyprutils_string "string")
|
||||||
|
add_dependencies(tests hyprutils_string)
|
||||||
|
|
||||||
|
add_executable(hyprutils_signal "tests/signal.cpp")
|
||||||
|
target_link_libraries(hyprutils_signal PRIVATE hyprutils)
|
||||||
|
add_test(NAME "Signal" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND hyprutils_signal "signal")
|
||||||
|
add_dependencies(tests hyprutils_signal)
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
install(TARGETS hyprutils)
|
||||||
|
install(DIRECTORY "include/hyprutils" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
10
hyprutils.pc.in
Normal file
10
hyprutils.pc.in
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
prefix=@PREFIX@
|
||||||
|
includedir=@INCLUDE@
|
||||||
|
libdir=@LIBDIR@
|
||||||
|
|
||||||
|
Name: hyprutils
|
||||||
|
URL: https://github.com/hyprwm/hyprutils
|
||||||
|
Description: Hyprland utilities library used across the ecosystem
|
||||||
|
Version: @HYPRUTILS_VERSION@
|
||||||
|
Cflags: -I${includedir}
|
||||||
|
Libs: -L${libdir} -lhyprutils
|
302
include/hyprutils/memory/SharedPtr.hpp
Normal file
302
include/hyprutils/memory/SharedPtr.hpp
Normal file
|
@ -0,0 +1,302 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
/*
|
||||||
|
This is a custom impl of std::shared_ptr.
|
||||||
|
It is not thread-safe like the STL one,
|
||||||
|
but Hyprland is single-threaded anyways.
|
||||||
|
|
||||||
|
It differs a bit from how the STL one works,
|
||||||
|
namely in the fact that it keeps the T* inside the
|
||||||
|
control block, and that you can still make a CWeakPtr
|
||||||
|
or deref an existing one inside the destructor.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyprutils {
|
||||||
|
namespace Memory {
|
||||||
|
namespace CSharedPointer_ {
|
||||||
|
class impl_base {
|
||||||
|
public:
|
||||||
|
virtual ~impl_base(){};
|
||||||
|
|
||||||
|
virtual void inc() noexcept = 0;
|
||||||
|
virtual void dec() noexcept = 0;
|
||||||
|
virtual void incWeak() noexcept = 0;
|
||||||
|
virtual void decWeak() noexcept = 0;
|
||||||
|
virtual unsigned int ref() noexcept = 0;
|
||||||
|
virtual unsigned int wref() noexcept = 0;
|
||||||
|
virtual void destroy() noexcept = 0;
|
||||||
|
virtual bool destroying() noexcept = 0;
|
||||||
|
virtual bool dataNonNull() noexcept = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class impl : public impl_base {
|
||||||
|
public:
|
||||||
|
impl(T* data) noexcept : _data(data) {
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* strong refcount */
|
||||||
|
unsigned int _ref = 0;
|
||||||
|
/* weak refcount */
|
||||||
|
unsigned int _weak = 0;
|
||||||
|
|
||||||
|
T* _data = nullptr;
|
||||||
|
|
||||||
|
friend void swap(impl*& a, impl*& b) {
|
||||||
|
impl* tmp = a;
|
||||||
|
a = b;
|
||||||
|
b = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* if the destructor was called,
|
||||||
|
creating shared_ptrs is no longer valid */
|
||||||
|
bool _destroying = false;
|
||||||
|
|
||||||
|
void _destroy() {
|
||||||
|
if (!_data || _destroying)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// first, we destroy the data, but keep the pointer.
|
||||||
|
// this way, weak pointers will still be able to
|
||||||
|
// reference and use, but no longer create shared ones.
|
||||||
|
_destroying = true;
|
||||||
|
__deleter(_data);
|
||||||
|
// now, we can reset the data and call it a day.
|
||||||
|
_data = nullptr;
|
||||||
|
_destroying = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::default_delete<T> __deleter{};
|
||||||
|
|
||||||
|
//
|
||||||
|
virtual void inc() noexcept {
|
||||||
|
_ref++;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void dec() noexcept {
|
||||||
|
_ref--;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void incWeak() noexcept {
|
||||||
|
_weak++;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void decWeak() noexcept {
|
||||||
|
_weak--;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual unsigned int ref() noexcept {
|
||||||
|
return _ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual unsigned int wref() noexcept {
|
||||||
|
return _weak;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void destroy() noexcept {
|
||||||
|
_destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool destroying() noexcept {
|
||||||
|
return _destroying;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool dataNonNull() noexcept {
|
||||||
|
return _data;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~impl() {
|
||||||
|
destroy();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class CSharedPointer {
|
||||||
|
public:
|
||||||
|
template <typename X>
|
||||||
|
using validHierarchy = typename std::enable_if<std::is_assignable<CSharedPointer<T>&, X>::value, CSharedPointer&>::type;
|
||||||
|
template <typename X>
|
||||||
|
using isConstructible = typename std::enable_if<std::is_constructible<T&, X&>::value>::type;
|
||||||
|
|
||||||
|
/* creates a new shared pointer managing a resource
|
||||||
|
avoid calling. Could duplicate ownership. Prefer makeShared */
|
||||||
|
explicit CSharedPointer(T* object) noexcept {
|
||||||
|
impl_ = new CSharedPointer_::impl<T>(object);
|
||||||
|
increment();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* creates a shared pointer from a reference */
|
||||||
|
template <typename U, typename = isConstructible<U>>
|
||||||
|
CSharedPointer(const CSharedPointer<U>& ref) noexcept {
|
||||||
|
impl_ = ref.impl_;
|
||||||
|
increment();
|
||||||
|
}
|
||||||
|
|
||||||
|
CSharedPointer(const CSharedPointer& ref) noexcept {
|
||||||
|
impl_ = ref.impl_;
|
||||||
|
increment();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename U, typename = isConstructible<U>>
|
||||||
|
CSharedPointer(CSharedPointer<U>&& ref) noexcept {
|
||||||
|
std::swap(impl_, ref.impl_);
|
||||||
|
}
|
||||||
|
|
||||||
|
CSharedPointer(CSharedPointer&& ref) noexcept {
|
||||||
|
std::swap(impl_, ref.impl_);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* allows weakPointer to create from an impl */
|
||||||
|
CSharedPointer(CSharedPointer_::impl_base* implementation) noexcept {
|
||||||
|
impl_ = implementation;
|
||||||
|
increment();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* creates an empty shared pointer with no implementation */
|
||||||
|
CSharedPointer() noexcept {
|
||||||
|
; // empty
|
||||||
|
}
|
||||||
|
|
||||||
|
/* creates an empty shared pointer with no implementation */
|
||||||
|
CSharedPointer(std::nullptr_t) noexcept {
|
||||||
|
; // empty
|
||||||
|
}
|
||||||
|
|
||||||
|
~CSharedPointer() {
|
||||||
|
// we do not decrement here,
|
||||||
|
// because we want to preserve the pointer
|
||||||
|
// in case this is the last owner.
|
||||||
|
if (impl_ && impl_->ref() == 1)
|
||||||
|
destroyImpl();
|
||||||
|
else
|
||||||
|
decrement();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename U>
|
||||||
|
validHierarchy<const CSharedPointer<U>&> operator=(const CSharedPointer<U>& rhs) {
|
||||||
|
if (impl_ == rhs.impl_)
|
||||||
|
return *this;
|
||||||
|
|
||||||
|
decrement();
|
||||||
|
impl_ = rhs.impl_;
|
||||||
|
increment();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
CSharedPointer& operator=(const CSharedPointer& rhs) {
|
||||||
|
if (impl_ == rhs.impl_)
|
||||||
|
return *this;
|
||||||
|
|
||||||
|
decrement();
|
||||||
|
impl_ = rhs.impl_;
|
||||||
|
increment();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename U>
|
||||||
|
validHierarchy<const CSharedPointer<U>&> operator=(CSharedPointer<U>&& rhs) {
|
||||||
|
std::swap(impl_, rhs.impl_);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
CSharedPointer& operator=(CSharedPointer&& rhs) {
|
||||||
|
std::swap(impl_, rhs.impl_);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
operator bool() const {
|
||||||
|
return impl_ && impl_->dataNonNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const CSharedPointer& rhs) const {
|
||||||
|
return impl_ == rhs.impl_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator()(const CSharedPointer& lhs, const CSharedPointer& rhs) const {
|
||||||
|
return (uintptr_t)lhs.impl_ < (uintptr_t)rhs.impl_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator<(const CSharedPointer& rhs) const {
|
||||||
|
return (uintptr_t)impl_ < (uintptr_t)rhs.impl_;
|
||||||
|
}
|
||||||
|
|
||||||
|
T* operator->() const {
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
T& operator*() const {
|
||||||
|
return *get();
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
decrement();
|
||||||
|
impl_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
T* get() const {
|
||||||
|
return (T*)(impl_ ? static_cast<CSharedPointer_::impl<T>*>(impl_)->_data : nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int strongRef() const {
|
||||||
|
return impl_ ? impl_->ref() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
CSharedPointer_::impl_base* impl_ = nullptr;
|
||||||
|
|
||||||
|
private:
|
||||||
|
/*
|
||||||
|
no-op if there is no impl_
|
||||||
|
may delete the stored object if ref == 0
|
||||||
|
may delete and reset impl_ if ref == 0 and weak == 0
|
||||||
|
*/
|
||||||
|
void decrement() {
|
||||||
|
if (!impl_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
impl_->dec();
|
||||||
|
|
||||||
|
// if ref == 0, we can destroy impl
|
||||||
|
if (impl_->ref() == 0)
|
||||||
|
destroyImpl();
|
||||||
|
}
|
||||||
|
/* no-op if there is no impl_ */
|
||||||
|
void increment() {
|
||||||
|
if (!impl_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
impl_->inc();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* destroy the pointed-to object
|
||||||
|
if able, will also destroy impl */
|
||||||
|
void destroyImpl() {
|
||||||
|
// destroy the impl contents
|
||||||
|
impl_->destroy();
|
||||||
|
|
||||||
|
// check for weak refs, if zero, we can also delete impl_
|
||||||
|
if (impl_->wref() == 0) {
|
||||||
|
delete impl_;
|
||||||
|
impl_ = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename U, typename... Args>
|
||||||
|
static CSharedPointer<U> makeShared(Args&&... args) {
|
||||||
|
return CSharedPointer<U>(new U(std::forward<Args>(args)...));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct std::hash<Hyprutils::Memory::CSharedPointer<T>> {
|
||||||
|
std::size_t operator()(const Hyprutils::Memory::CSharedPointer<T>& p) const noexcept {
|
||||||
|
return std::hash<void*>{}(p.impl_);
|
||||||
|
}
|
||||||
|
};
|
192
include/hyprutils/memory/WeakPtr.hpp
Normal file
192
include/hyprutils/memory/WeakPtr.hpp
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "./SharedPtr.hpp"
|
||||||
|
|
||||||
|
/*
|
||||||
|
This is a Hyprland implementation of std::weak_ptr.
|
||||||
|
|
||||||
|
See SharedPtr.hpp for more info on how it's different.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyprutils {
|
||||||
|
namespace Memory {
|
||||||
|
template <typename T>
|
||||||
|
class CWeakPointer {
|
||||||
|
public:
|
||||||
|
template <typename X>
|
||||||
|
using validHierarchy = typename std::enable_if<std::is_assignable<CWeakPointer<T>&, X>::value, CWeakPointer&>::type;
|
||||||
|
template <typename X>
|
||||||
|
using isConstructible = typename std::enable_if<std::is_constructible<T&, X&>::value>::type;
|
||||||
|
|
||||||
|
/* create a weak ptr from a reference */
|
||||||
|
template <typename U, typename = isConstructible<U>>
|
||||||
|
CWeakPointer(const CSharedPointer<U>& ref) noexcept {
|
||||||
|
if (!ref.impl_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
impl_ = ref.impl_;
|
||||||
|
incrementWeak();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* create a weak ptr from another weak ptr */
|
||||||
|
template <typename U, typename = isConstructible<U>>
|
||||||
|
CWeakPointer(const CWeakPointer<U>& ref) noexcept {
|
||||||
|
if (!ref.impl_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
impl_ = ref.impl_;
|
||||||
|
incrementWeak();
|
||||||
|
}
|
||||||
|
|
||||||
|
CWeakPointer(const CWeakPointer& ref) noexcept {
|
||||||
|
if (!ref.impl_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
impl_ = ref.impl_;
|
||||||
|
incrementWeak();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename U, typename = isConstructible<U>>
|
||||||
|
CWeakPointer(CWeakPointer<U>&& ref) noexcept {
|
||||||
|
std::swap(impl_, ref.impl_);
|
||||||
|
}
|
||||||
|
|
||||||
|
CWeakPointer(CWeakPointer&& ref) noexcept {
|
||||||
|
std::swap(impl_, ref.impl_);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* create a weak ptr from another weak ptr with assignment */
|
||||||
|
template <typename U>
|
||||||
|
validHierarchy<const CWeakPointer<U>&> operator=(const CWeakPointer<U>& rhs) {
|
||||||
|
if (impl_ == rhs.impl_)
|
||||||
|
return *this;
|
||||||
|
|
||||||
|
decrementWeak();
|
||||||
|
impl_ = rhs.impl_;
|
||||||
|
incrementWeak();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
CWeakPointer<T>& operator=(const CWeakPointer& rhs) {
|
||||||
|
if (impl_ == rhs.impl_)
|
||||||
|
return *this;
|
||||||
|
|
||||||
|
decrementWeak();
|
||||||
|
impl_ = rhs.impl_;
|
||||||
|
incrementWeak();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* create a weak ptr from a shared ptr with assignment */
|
||||||
|
template <typename U>
|
||||||
|
validHierarchy<const CWeakPointer<U>&> operator=(const CSharedPointer<U>& rhs) {
|
||||||
|
if ((uintptr_t)impl_ == (uintptr_t)rhs.impl_)
|
||||||
|
return *this;
|
||||||
|
|
||||||
|
decrementWeak();
|
||||||
|
impl_ = rhs.impl_;
|
||||||
|
incrementWeak();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* create an empty weak ptr */
|
||||||
|
CWeakPointer() {
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
~CWeakPointer() {
|
||||||
|
decrementWeak();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* expired MAY return true even if the pointer is still stored.
|
||||||
|
the situation would be e.g. self-weak pointer in a destructor.
|
||||||
|
for pointer validity, use valid() */
|
||||||
|
bool expired() const {
|
||||||
|
return !impl_ || !impl_->dataNonNull() || impl_->destroying();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* this means the pointed-to object is not yet deleted and can still be
|
||||||
|
referenced, but it might be in the process of being deleted.
|
||||||
|
check !expired() if you want to check whether it's valid and
|
||||||
|
assignable to a SP. */
|
||||||
|
bool valid() const {
|
||||||
|
return impl_ && impl_->dataNonNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
decrementWeak();
|
||||||
|
impl_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
CSharedPointer<T> lock() const {
|
||||||
|
if (!impl_ || !impl_->dataNonNull() || impl_->destroying())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return CSharedPointer<T>(impl_);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* this returns valid() */
|
||||||
|
operator bool() const {
|
||||||
|
return valid();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const CWeakPointer<T>& rhs) const {
|
||||||
|
return impl_ == rhs.impl_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const CSharedPointer<T>& rhs) const {
|
||||||
|
return impl_ == rhs.impl_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator()(const CWeakPointer& lhs, const CWeakPointer& rhs) const {
|
||||||
|
return (uintptr_t)lhs.impl_ < (uintptr_t)rhs.impl_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator<(const CWeakPointer& rhs) const {
|
||||||
|
return (uintptr_t)impl_ < (uintptr_t)rhs.impl_;
|
||||||
|
}
|
||||||
|
|
||||||
|
T* get() const {
|
||||||
|
return (T*)(impl_ ? static_cast<CSharedPointer_::impl<T>*>(impl_)->_data : nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
T* operator->() const {
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
CSharedPointer_::impl_base* impl_ = nullptr;
|
||||||
|
|
||||||
|
private:
|
||||||
|
/* no-op if there is no impl_ */
|
||||||
|
void decrementWeak() {
|
||||||
|
if (!impl_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
impl_->decWeak();
|
||||||
|
|
||||||
|
// we need to check for ->destroying,
|
||||||
|
// because otherwise we could destroy here
|
||||||
|
// and have a shared_ptr destroy the same thing
|
||||||
|
// later (in situations where we have a weak_ptr to self)
|
||||||
|
if (impl_->wref() == 0 && impl_->ref() == 0 && !impl_->destroying()) {
|
||||||
|
delete impl_;
|
||||||
|
impl_ = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* no-op if there is no impl_ */
|
||||||
|
void incrementWeak() {
|
||||||
|
if (!impl_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
impl_->incWeak();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct std::hash<Hyprutils::Memory::CWeakPointer<T>> {
|
||||||
|
std::size_t operator()(const Hyprutils::Memory::CWeakPointer<T>& p) const noexcept {
|
||||||
|
return std::hash<void*>{}(p.impl_);
|
||||||
|
}
|
||||||
|
};
|
44
include/hyprutils/signal/Listener.hpp
Normal file
44
include/hyprutils/signal/Listener.hpp
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <any>
|
||||||
|
#include <functional>
|
||||||
|
#include <hyprutils/memory/SharedPtr.hpp>
|
||||||
|
|
||||||
|
namespace Hyprutils {
|
||||||
|
namespace Signal {
|
||||||
|
class CSignal;
|
||||||
|
|
||||||
|
class CSignalListener {
|
||||||
|
public:
|
||||||
|
CSignalListener(std::function<void(std::any)> handler);
|
||||||
|
|
||||||
|
CSignalListener(CSignalListener&&) = delete;
|
||||||
|
CSignalListener(CSignalListener&) = delete;
|
||||||
|
CSignalListener(const CSignalListener&) = delete;
|
||||||
|
CSignalListener(const CSignalListener&&) = delete;
|
||||||
|
|
||||||
|
void emit(std::any data);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::function<void(std::any)> m_fHandler;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef Hyprutils::Memory::CSharedPointer<CSignalListener> CHyprSignalListener;
|
||||||
|
|
||||||
|
class CStaticSignalListener {
|
||||||
|
public:
|
||||||
|
CStaticSignalListener(std::function<void(void*, std::any)> handler, void* owner);
|
||||||
|
|
||||||
|
CStaticSignalListener(CStaticSignalListener&&) = delete;
|
||||||
|
CStaticSignalListener(CStaticSignalListener&) = delete;
|
||||||
|
CStaticSignalListener(const CStaticSignalListener&) = delete;
|
||||||
|
CStaticSignalListener(const CStaticSignalListener&&) = delete;
|
||||||
|
|
||||||
|
void emit(std::any data);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void* m_pOwner = nullptr;
|
||||||
|
std::function<void(void*, std::any)> m_fHandler;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
28
include/hyprutils/signal/Signal.hpp
Normal file
28
include/hyprutils/signal/Signal.hpp
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <any>
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
#include <hyprutils/memory/WeakPtr.hpp>
|
||||||
|
#include "./Listener.hpp"
|
||||||
|
|
||||||
|
namespace Hyprutils {
|
||||||
|
namespace Signal {
|
||||||
|
class CSignal {
|
||||||
|
public:
|
||||||
|
void emit(std::any data = {});
|
||||||
|
|
||||||
|
//
|
||||||
|
[[nodiscard("Listener is unregistered when the ptr is lost")]] CHyprSignalListener registerListener(std::function<void(std::any)> handler);
|
||||||
|
|
||||||
|
// this is for static listeners. They die with this signal.
|
||||||
|
// TODO: can we somehow rid of the void* data and make it a custom this?
|
||||||
|
void registerStaticListener(std::function<void(void*, std::any)> handler, void* owner);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<Hyprutils::Memory::CWeakPointer<CSignalListener>> m_vListeners;
|
||||||
|
std::vector<std::unique_ptr<CStaticSignalListener>> m_vStaticListeners;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
10
include/hyprutils/string/String.hpp
Normal file
10
include/hyprutils/string/String.hpp
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Hyprutils {
|
||||||
|
namespace String {
|
||||||
|
// trims beginning and end of whitespace characters
|
||||||
|
std::string trim(const std::string& in);
|
||||||
|
bool isNumber(const std::string& str, bool allowfloat = false);
|
||||||
|
};
|
||||||
|
};
|
22
src/signal/Listener.cpp
Normal file
22
src/signal/Listener.cpp
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
#include <hyprutils/signal/Listener.hpp>
|
||||||
|
|
||||||
|
using namespace Hyprutils::Signal;
|
||||||
|
|
||||||
|
Hyprutils::Signal::CSignalListener::CSignalListener(std::function<void(std::any)> handler) : m_fHandler(handler) {
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Hyprutils::Signal::CSignalListener::emit(std::any data) {
|
||||||
|
if (!m_fHandler)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_fHandler(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
Hyprutils::Signal::CStaticSignalListener::CStaticSignalListener(std::function<void(void*, std::any)> handler, void* owner) : m_pOwner(owner), m_fHandler(handler) {
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Hyprutils::Signal::CStaticSignalListener::emit(std::any data) {
|
||||||
|
m_fHandler(m_pOwner, data);
|
||||||
|
}
|
58
src/signal/Signal.cpp
Normal file
58
src/signal/Signal.cpp
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
#include <hyprutils/signal/Signal.hpp>
|
||||||
|
#include <hyprutils/memory/WeakPtr.hpp>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
using namespace Hyprutils::Signal;
|
||||||
|
using namespace Hyprutils::Memory;
|
||||||
|
|
||||||
|
#define SP CSharedPointer
|
||||||
|
#define WP CWeakPointer
|
||||||
|
|
||||||
|
void Hyprutils::Signal::CSignal::emit(std::any data) {
|
||||||
|
bool dirty = false;
|
||||||
|
|
||||||
|
std::vector<SP<CSignalListener>> listeners;
|
||||||
|
for (auto& l : m_vListeners) {
|
||||||
|
if (l.expired()) {
|
||||||
|
dirty = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
listeners.emplace_back(l.lock());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<CStaticSignalListener*> statics;
|
||||||
|
for (auto& l : m_vStaticListeners) {
|
||||||
|
statics.emplace_back(l.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& l : listeners) {
|
||||||
|
// if there is only one lock, it means the event is only held by the listeners
|
||||||
|
// vector and was removed during our iteration
|
||||||
|
if (l.strongRef() == 1) {
|
||||||
|
dirty = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
l->emit(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& l : statics) {
|
||||||
|
l->emit(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// release SPs
|
||||||
|
listeners.clear();
|
||||||
|
|
||||||
|
if (dirty)
|
||||||
|
std::erase_if(m_vListeners, [](const auto& other) { return other.expired(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
CHyprSignalListener Hyprutils::Signal::CSignal::registerListener(std::function<void(std::any)> handler) {
|
||||||
|
CHyprSignalListener listener = makeShared<CSignalListener>(handler);
|
||||||
|
m_vListeners.emplace_back(listener);
|
||||||
|
return listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Hyprutils::Signal::CSignal::registerStaticListener(std::function<void(void*, std::any)> handler, void* owner) {
|
||||||
|
m_vStaticListeners.emplace_back(std::make_unique<CStaticSignalListener>(handler, owner));
|
||||||
|
}
|
58
src/string/String.cpp
Normal file
58
src/string/String.cpp
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
#include <algorithm>
|
||||||
|
#include <hyprutils/string/String.hpp>
|
||||||
|
|
||||||
|
using namespace Hyprutils::String;
|
||||||
|
|
||||||
|
std::string Hyprutils::String::trim(const std::string& in) {
|
||||||
|
if (in.empty())
|
||||||
|
return in;
|
||||||
|
|
||||||
|
int countBefore = 0;
|
||||||
|
while (countBefore < in.length() && std::isspace(in.at(countBefore))) {
|
||||||
|
countBefore++;
|
||||||
|
}
|
||||||
|
|
||||||
|
int countAfter = 0;
|
||||||
|
while (countAfter < in.length() - countBefore && std::isspace(in.at(in.length() - countAfter - 1))) {
|
||||||
|
countAfter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string result = in.substr(countBefore, in.length() - countBefore - countAfter);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Hyprutils::String::isNumber(const std::string& str, bool allowfloat) {
|
||||||
|
if (str.empty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < str.length(); ++i) {
|
||||||
|
const char& c = str.at(i);
|
||||||
|
|
||||||
|
if (i == 0 && str.at(i) == '-') {
|
||||||
|
// only place where we allow -
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isdigit(c)) {
|
||||||
|
if (!allowfloat)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (c != '.')
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (i == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (str.at(0) == '-')
|
||||||
|
return false;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (str.back() == '.')
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
29
tests/memory.cpp
Normal file
29
tests/memory.cpp
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
#include <hyprutils/memory/WeakPtr.hpp>
|
||||||
|
#include "shared.hpp"
|
||||||
|
|
||||||
|
using namespace Hyprutils::Memory;
|
||||||
|
|
||||||
|
#define SP CSharedPointer
|
||||||
|
#define WP CWeakPointer
|
||||||
|
|
||||||
|
int main(int argc, char** argv, char** envp) {
|
||||||
|
SP<int> intPtr = makeShared<int>(10);
|
||||||
|
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
EXPECT(*intPtr, 10);
|
||||||
|
EXPECT(intPtr.strongRef(), 1);
|
||||||
|
|
||||||
|
WP<int> weak = intPtr;
|
||||||
|
|
||||||
|
EXPECT(*intPtr, 10);
|
||||||
|
EXPECT(intPtr.strongRef(), 1);
|
||||||
|
EXPECT(*weak.get(), 10);
|
||||||
|
EXPECT(weak.expired(), false);
|
||||||
|
|
||||||
|
intPtr = {};
|
||||||
|
|
||||||
|
EXPECT(weak.expired(), true);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
20
tests/shared.hpp
Normal file
20
tests/shared.hpp
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
#pragma once
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace Colors {
|
||||||
|
constexpr const char* RED = "\x1b[31m";
|
||||||
|
constexpr const char* GREEN = "\x1b[32m";
|
||||||
|
constexpr const char* YELLOW = "\x1b[33m";
|
||||||
|
constexpr const char* BLUE = "\x1b[34m";
|
||||||
|
constexpr const char* MAGENTA = "\x1b[35m";
|
||||||
|
constexpr const char* CYAN = "\x1b[36m";
|
||||||
|
constexpr const char* RESET = "\x1b[0m";
|
||||||
|
};
|
||||||
|
|
||||||
|
#define EXPECT(expr, val) \
|
||||||
|
if (const auto RESULT = expr; RESULT != (val)) { \
|
||||||
|
std::cout << Colors::RED << "Failed: " << Colors::RESET << #expr << ", expected " << #val << " but got " << RESULT << "\n"; \
|
||||||
|
ret = 1; \
|
||||||
|
} else { \
|
||||||
|
std::cout << Colors::GREEN << "Passed " << Colors::RESET << #expr << ". Got " << val << "\n"; \
|
||||||
|
}
|
30
tests/signal.cpp
Normal file
30
tests/signal.cpp
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
#include <hyprutils/signal/Signal.hpp>
|
||||||
|
#include <hyprutils/memory/WeakPtr.hpp>
|
||||||
|
#include "shared.hpp"
|
||||||
|
|
||||||
|
using namespace Hyprutils::Signal;
|
||||||
|
using namespace Hyprutils::Memory;
|
||||||
|
|
||||||
|
int main(int argc, char** argv, char** envp) {
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
CSignal signal;
|
||||||
|
int data = 0;
|
||||||
|
auto listener = signal.registerListener([&] (std::any d) {
|
||||||
|
data = 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
signal.emit();
|
||||||
|
|
||||||
|
EXPECT(data, 1);
|
||||||
|
|
||||||
|
data = 0;
|
||||||
|
|
||||||
|
listener.reset();
|
||||||
|
|
||||||
|
signal.emit();
|
||||||
|
|
||||||
|
EXPECT(data, 0);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
33
tests/string.cpp
Normal file
33
tests/string.cpp
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
#include <hyprutils/string/String.hpp>
|
||||||
|
#include "shared.hpp"
|
||||||
|
|
||||||
|
using namespace Hyprutils::String;
|
||||||
|
|
||||||
|
int main(int argc, char** argv, char** envp) {
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
EXPECT(trim(" a "), "a");
|
||||||
|
EXPECT(trim(" a a "), "a a");
|
||||||
|
EXPECT(trim("a"), "a");
|
||||||
|
EXPECT(trim(" "), "");
|
||||||
|
|
||||||
|
EXPECT(isNumber("99214123434"), true);
|
||||||
|
EXPECT(isNumber("-35252345234"), true);
|
||||||
|
EXPECT(isNumber("---3423--432"), false);
|
||||||
|
EXPECT(isNumber("s---3423--432"), false);
|
||||||
|
EXPECT(isNumber("---3423--432s"), false);
|
||||||
|
EXPECT(isNumber("1s"), false);
|
||||||
|
EXPECT(isNumber(""), false);
|
||||||
|
EXPECT(isNumber("--0"), false);
|
||||||
|
EXPECT(isNumber("abc"), false);
|
||||||
|
EXPECT(isNumber("0.0", true), true);
|
||||||
|
EXPECT(isNumber("0.2", true), true);
|
||||||
|
EXPECT(isNumber("0.", true), false);
|
||||||
|
EXPECT(isNumber(".0", true), false);
|
||||||
|
EXPECT(isNumber("", true), false);
|
||||||
|
EXPECT(isNumber("vvss", true), false);
|
||||||
|
EXPECT(isNumber("0.9999s", true), false);
|
||||||
|
EXPECT(isNumber("s0.9999", true), false);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
Loading…
Reference in a new issue