From 32e967a6b278958a7b118887184c7971392cb47a Mon Sep 17 00:00:00 2001 From: kunitoki Date: Sun, 7 Dec 2025 00:55:13 +0100 Subject: [PATCH 01/30] Improve expression --- modules/yup_core/maths/yup_Expression.cpp | 13 +- tests/yup_core/yup_Expression.cpp | 1291 +++++++++++++++++++++ 2 files changed, 1301 insertions(+), 3 deletions(-) create mode 100644 tests/yup_core/yup_Expression.cpp diff --git a/modules/yup_core/maths/yup_Expression.cpp b/modules/yup_core/maths/yup_Expression.cpp index 51ce10644..d39fbd4d4 100644 --- a/modules/yup_core/maths/yup_Expression.cpp +++ b/modules/yup_core/maths/yup_Expression.cpp @@ -1094,9 +1094,14 @@ Expression::Expression (const String& stringToParse, String& parseError) Expression Expression::parse (String::CharPointerType& stringToParse, String& parseError) { Helpers::Parser parser (stringToParse); - Expression e (parser.readUpToComma().get()); + + auto result = parser.readUpToComma(); parseError = parser.error; - return e; + + if (result) + return Expression (result.get()); + + return {}; } double Expression::evaluate() const @@ -1114,7 +1119,9 @@ double Expression::evaluate (const Scope& scope, String& evaluationError) const { try { - return term->resolve (scope, 0)->toDouble(); + if (term != nullptr) + if (auto result = term->resolve (scope, 0)) + return result->toDouble(); } catch (Helpers::EvaluationError& e) { diff --git a/tests/yup_core/yup_Expression.cpp b/tests/yup_core/yup_Expression.cpp new file mode 100644 index 000000000..af6bd2b3c --- /dev/null +++ b/tests/yup_core/yup_Expression.cpp @@ -0,0 +1,1291 @@ +/* + ============================================================================== + + This file is part of the YUP library. + Copyright (c) 2025 - kunitoki@gmail.com + + YUP is an open source library subject to open-source licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + to use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + YUP IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +#include + +#include + +using namespace yup; + +namespace +{ +// Helper scope for testing custom symbols and functions +class TestScope : public Expression::Scope +{ +public: + TestScope() + { + symbols.set ("x", 5.0); + symbols.set ("y", 10.0); + symbols.set ("pi", 3.14159265358979323846); + } + + String getScopeUID() const override + { + return "TestScope"; + } + + Expression getSymbolValue (const String& symbol) const override + { + if (symbols.contains (symbol)) + return Expression (symbols[symbol]); + + return Expression::Scope::getSymbolValue (symbol); + } + + double evaluateFunction (const String& functionName, const double* parameters, int numParams) const override + { + if (functionName == "square" && numParams == 1) + return parameters[0] * parameters[0]; + + if (functionName == "add" && numParams == 2) + return parameters[0] + parameters[1]; + + return Expression::Scope::evaluateFunction (functionName, parameters, numParams); + } + + HashMap symbols; +}; + +// Helper scope for testing dot operator +class NestedScope : public Expression::Scope +{ +public: + NestedScope() + { + innerValue = 42.0; + } + + String getScopeUID() const override + { + return "NestedScope"; + } + + Expression getSymbolValue (const String& symbol) const override + { + if (symbol == "value") + return Expression (innerValue); + + return Expression::Scope::getSymbolValue (symbol); + } + + double innerValue; +}; + +class OuterScope : public Expression::Scope +{ +public: + String getScopeUID() const override + { + return "OuterScope"; + } + + void visitRelativeScope (const String& scopeName, Visitor& visitor) const override + { + if (scopeName == "inner") + { + NestedScope nested; + visitor.visit (nested); + } + else + { + Expression::Scope::visitRelativeScope (scopeName, visitor); + } + } +}; +} // namespace + +// ============================================================================== +// Constructor Tests +// ============================================================================== + +TEST (ExpressionTests, DefaultConstructorCreatesZero) +{ + Expression e; + EXPECT_EQ (0.0, e.evaluate()); + EXPECT_EQ (Expression::constantType, e.getType()); +} + +TEST (ExpressionTests, ConstantConstructorCreatesCorrectValue) +{ + Expression e (42.5); + EXPECT_EQ (42.5, e.evaluate()); + EXPECT_EQ (Expression::constantType, e.getType()); +} + +TEST (ExpressionTests, NegativeConstantConstructor) +{ + Expression e (-123.45); + EXPECT_EQ (-123.45, e.evaluate()); +} + +TEST (ExpressionTests, CopyConstructorCreatesIndependentCopy) +{ + Expression e1 (100.0); + Expression e2 (e1); + + EXPECT_EQ (100.0, e2.evaluate()); + EXPECT_EQ (e1.evaluate(), e2.evaluate()); +} + +TEST (ExpressionTests, CopyAssignmentOperator) +{ + Expression e1 (200.0); + Expression e2; + + e2 = e1; + EXPECT_EQ (200.0, e2.evaluate()); +} + +TEST (ExpressionTests, MoveConstructor) +{ + Expression e1 (300.0); + Expression e2 (std::move (e1)); + + EXPECT_EQ (300.0, e2.evaluate()); +} + +TEST (ExpressionTests, MoveAssignmentOperator) +{ + Expression e1 (400.0); + Expression e2; + + e2 = std::move (e1); + EXPECT_EQ (400.0, e2.evaluate()); +} + +// ============================================================================== +// String Parsing Tests +// ============================================================================== + +TEST (ExpressionTests, ParseSimpleNumber) +{ + String error; + Expression e ("42", error); + + EXPECT_TRUE (error.isEmpty()); + EXPECT_EQ (42.0, e.evaluate()); +} + +TEST (ExpressionTests, ParseDecimalNumber) +{ + String error; + Expression e ("3.14159", error); + + EXPECT_TRUE (error.isEmpty()); + EXPECT_NEAR (3.14159, e.evaluate(), 0.00001); +} + +TEST (ExpressionTests, ParseNegativeNumber) +{ + String error; + Expression e ("-99.5", error); + + EXPECT_TRUE (error.isEmpty()); + EXPECT_EQ (-99.5, e.evaluate()); +} + +TEST (ExpressionTests, ParseAddition) +{ + String error; + Expression e ("10 + 20", error); + + EXPECT_TRUE (error.isEmpty()); + EXPECT_EQ (30.0, e.evaluate()); +} + +TEST (ExpressionTests, ParseSubtraction) +{ + String error; + Expression e ("50 - 30", error); + + EXPECT_TRUE (error.isEmpty()); + EXPECT_EQ (20.0, e.evaluate()); +} + +TEST (ExpressionTests, ParseMultiplication) +{ + String error; + Expression e ("6 * 7", error); + + EXPECT_TRUE (error.isEmpty()); + EXPECT_EQ (42.0, e.evaluate()); +} + +TEST (ExpressionTests, ParseDivision) +{ + String error; + Expression e ("100 / 4", error); + + EXPECT_TRUE (error.isEmpty()); + EXPECT_EQ (25.0, e.evaluate()); +} + +TEST (ExpressionTests, ParseComplexExpression) +{ + String error; + Expression e ("2 + 3 * 4", error); + + EXPECT_TRUE (error.isEmpty()); + EXPECT_EQ (14.0, e.evaluate()); // Respects operator precedence +} + +TEST (ExpressionTests, ParseExpressionWithParentheses) +{ + String error; + Expression e ("(2 + 3) * 4", error); + + EXPECT_TRUE (error.isEmpty()); + EXPECT_EQ (20.0, e.evaluate()); +} + +TEST (ExpressionTests, ParseNestedParentheses) +{ + String error; + Expression e ("((2 + 3) * (4 + 1))", error); + + EXPECT_TRUE (error.isEmpty()); + EXPECT_EQ (25.0, e.evaluate()); +} + +TEST (ExpressionTests, ParseUnaryMinus) +{ + String error; + Expression e ("-(5 + 3)", error); + + EXPECT_TRUE (error.isEmpty()); + EXPECT_EQ (-8.0, e.evaluate()); +} + +TEST (ExpressionTests, ParseUnaryPlus) +{ + String error; + Expression e ("+42", error); + + EXPECT_TRUE (error.isEmpty()); + EXPECT_EQ (42.0, e.evaluate()); +} + +TEST (ExpressionTests, ParseInvalidSyntaxReturnsError) +{ + String error; + Expression e ("10 +", error); + + EXPECT_FALSE (error.isEmpty()); +} + +TEST (ExpressionTests, ParseInvalidCharactersReturnsError) +{ + String error; + Expression e ("10 $ 20", error); + + EXPECT_FALSE (error.isEmpty()); +} + +TEST (ExpressionTests, ParseEmptyString) +{ + String error; + Expression e ("", error); + + EXPECT_TRUE (error.isEmpty()); + EXPECT_EQ (0.0, e.evaluate()); +} + +TEST (ExpressionTests, ParseWhitespaceOnly) +{ + String error; + Expression e (" ", error); + + EXPECT_FALSE (error.isEmpty()); + EXPECT_EQ (0.0, e.evaluate()); +} + +// ============================================================================== +// Arithmetic Operator Tests +// ============================================================================== + +TEST (ExpressionTests, AdditionOperator) +{ + Expression e1 (10.0); + Expression e2 (20.0); + Expression result = e1 + e2; + + EXPECT_EQ (30.0, result.evaluate()); + EXPECT_EQ (Expression::operatorType, result.getType()); +} + +TEST (ExpressionTests, SubtractionOperator) +{ + Expression e1 (50.0); + Expression e2 (20.0); + Expression result = e1 - e2; + + EXPECT_EQ (30.0, result.evaluate()); +} + +TEST (ExpressionTests, MultiplicationOperator) +{ + Expression e1 (6.0); + Expression e2 (7.0); + Expression result = e1 * e2; + + EXPECT_EQ (42.0, result.evaluate()); +} + +TEST (ExpressionTests, DivisionOperator) +{ + Expression e1 (100.0); + Expression e2 (4.0); + Expression result = e1 / e2; + + EXPECT_EQ (25.0, result.evaluate()); +} + +TEST (ExpressionTests, UnaryNegationOperator) +{ + Expression e (42.0); + Expression result = -e; + + EXPECT_EQ (-42.0, result.evaluate()); +} + +TEST (ExpressionTests, ChainedOperations) +{ + Expression a (2.0); + Expression b (3.0); + Expression c (4.0); + Expression result = a + b * c; + + EXPECT_EQ (14.0, result.evaluate()); +} + +TEST (ExpressionTests, DivisionByZeroReturnsInfinity) +{ + Expression e1 (10.0); + Expression e2 (0.0); + Expression result = e1 / e2; + + double value = result.evaluate(); + EXPECT_TRUE (std::isinf (value)); +} + +// ============================================================================== +// Built-in Function Tests +// ============================================================================== + +TEST (ExpressionTests, ParseSinFunction) +{ + String error; + Expression e ("sin(0)", error); + + EXPECT_TRUE (error.isEmpty()); + EXPECT_NEAR (0.0, e.evaluate(), 0.0001); +} + +TEST (ExpressionTests, ParseCosFunction) +{ + String error; + Expression e ("cos(0)", error); + + EXPECT_TRUE (error.isEmpty()); + EXPECT_NEAR (1.0, e.evaluate(), 0.0001); +} + +TEST (ExpressionTests, ParseTanFunction) +{ + String error; + Expression e ("tan(0)", error); + + EXPECT_TRUE (error.isEmpty()); + EXPECT_NEAR (0.0, e.evaluate(), 0.0001); +} + +TEST (ExpressionTests, ParseAbsFunction) +{ + String error; + Expression e ("abs(-42)", error); + + EXPECT_TRUE (error.isEmpty()); + EXPECT_EQ (42.0, e.evaluate()); +} + +TEST (ExpressionTests, ParseMinFunction) +{ + String error; + Expression e ("min(10, 20, 5, 30)", error); + + EXPECT_TRUE (error.isEmpty()); + EXPECT_EQ (5.0, e.evaluate()); +} + +TEST (ExpressionTests, ParseMaxFunction) +{ + String error; + Expression e ("max(10, 20, 5, 30)", error); + + EXPECT_TRUE (error.isEmpty()); + EXPECT_EQ (30.0, e.evaluate()); +} + +TEST (ExpressionTests, ParseMinFunctionWithTwoArgs) +{ + String error; + Expression e ("min(10, 5)", error); + + EXPECT_TRUE (error.isEmpty()); + EXPECT_EQ (5.0, e.evaluate()); +} + +TEST (ExpressionTests, ParseFunctionWithExpressionAsArgument) +{ + String error; + Expression e ("abs(5 - 10)", error); + + EXPECT_TRUE (error.isEmpty()); + EXPECT_EQ (5.0, e.evaluate()); +} + +TEST (ExpressionTests, ParseNestedFunctions) +{ + String error; + Expression e ("abs(sin(0) - 1)", error); + + EXPECT_TRUE (error.isEmpty()); + EXPECT_NEAR (1.0, e.evaluate(), 0.0001); +} + +TEST (ExpressionTests, ParseUnknownFunctionReturnsErrorOnEvaluation) +{ + String error; + Expression e ("unknownFunc(42)", error); + + EXPECT_TRUE (error.isEmpty()); // Parsing succeeds + EXPECT_EQ (Expression::functionType, e.getType()); + + String evalError; + e.evaluate (Expression::Scope(), evalError); + EXPECT_FALSE (evalError.isEmpty()); +} + +TEST (ExpressionTests, ParseEmptyFunctionCall) +{ + String error; + Expression e ("cos()", error); + + EXPECT_TRUE (error.isEmpty()); +} + +// ============================================================================== +// Symbol Tests +// ============================================================================== + +TEST (ExpressionTests, CreateSymbolExpression) +{ + Expression e = Expression::symbol ("x"); + + EXPECT_EQ (Expression::symbolType, e.getType()); + EXPECT_EQ ("x", e.getSymbolOrFunction()); + EXPECT_TRUE (e.usesAnySymbols()); +} + +TEST (ExpressionTests, ParseSymbolExpression) +{ + String error; + Expression e ("x + 10", error); + + EXPECT_TRUE (error.isEmpty()); + EXPECT_TRUE (e.usesAnySymbols()); +} + +TEST (ExpressionTests, EvaluateSymbolWithCustomScope) +{ + String error; + Expression e ("x + y", error); + TestScope scope; + + EXPECT_TRUE (error.isEmpty()); + EXPECT_EQ (15.0, e.evaluate (scope)); // x=5, y=10 +} + +TEST (ExpressionTests, EvaluateUnknownSymbolReturnsError) +{ + String error; + Expression e ("unknownSymbol", error); + + EXPECT_TRUE (error.isEmpty()); // Parsing succeeds + + String evalError; + e.evaluate (Expression::Scope(), evalError); + EXPECT_FALSE (evalError.isEmpty()); +} + +TEST (ExpressionTests, SymbolsInComplexExpression) +{ + String error; + Expression e ("x * 2 + y / 5", error); + TestScope scope; + + EXPECT_TRUE (error.isEmpty()); + EXPECT_EQ (12.0, e.evaluate (scope)); // (5*2) + (10/5) = 12 +} + +TEST (ExpressionTests, UsesAnySymbolsReturnsFalseForConstants) +{ + Expression e (42.0); + EXPECT_FALSE (e.usesAnySymbols()); +} + +TEST (ExpressionTests, UsesAnySymbolsReturnsTrueForSymbols) +{ + Expression e = Expression::symbol ("x"); + EXPECT_TRUE (e.usesAnySymbols()); +} + +TEST (ExpressionTests, ReferencesSymbolFindsSymbol) +{ + String error; + Expression e ("x + y", error); + TestScope scope; + + Expression::Symbol symbolX ("TestScope", "x"); + Expression::Symbol symbolY ("TestScope", "y"); + Expression::Symbol symbolZ ("TestScope", "z"); + + EXPECT_TRUE (e.referencesSymbol (symbolX, scope)); + EXPECT_TRUE (e.referencesSymbol (symbolY, scope)); + EXPECT_FALSE (e.referencesSymbol (symbolZ, scope)); +} + +TEST (ExpressionTests, FindReferencedSymbols) +{ + String error; + Expression e ("x + y * 2", error); + TestScope scope; + + Array symbols; + e.findReferencedSymbols (symbols, scope); + + EXPECT_EQ (2, symbols.size()); +} + +TEST (ExpressionTests, WithRenamedSymbol) +{ + String error; + Expression e ("x + 10", error); + TestScope scope; + + Expression::Symbol symbolX ("TestScope", "x"); + Expression renamed = e.withRenamedSymbol (symbolX, "newX", scope); + + EXPECT_EQ ("newX + 10", renamed.toString()); +} + +TEST (ExpressionTests, WithRenamedSymbolDoesNotChangeOriginal) +{ + String error; + Expression e ("x + 10", error); + TestScope scope; + + Expression::Symbol symbolX ("TestScope", "x"); + Expression renamed = e.withRenamedSymbol (symbolX, "newX", scope); + + EXPECT_EQ ("x + 10", e.toString()); + EXPECT_EQ ("newX + 10", renamed.toString()); +} + +// ============================================================================== +// Function Tests +// ============================================================================== + +TEST (ExpressionTests, CreateFunctionExpression) +{ + Array params; + params.add (Expression (5.0)); + + Expression e = Expression::function ("square", params); + + EXPECT_EQ (Expression::functionType, e.getType()); + EXPECT_EQ ("square", e.getSymbolOrFunction()); +} + +TEST (ExpressionTests, EvaluateFunctionWithCustomScope) +{ + String error; + Expression e ("square(5)", error); + TestScope scope; + + EXPECT_TRUE (error.isEmpty()); + EXPECT_EQ (25.0, e.evaluate (scope)); +} + +TEST (ExpressionTests, EvaluateFunctionWithMultipleParameters) +{ + String error; + Expression e ("add(10, 20)", error); + TestScope scope; + + EXPECT_TRUE (error.isEmpty()); + EXPECT_EQ (30.0, e.evaluate (scope)); +} + +TEST (ExpressionTests, FunctionWithSymbolArguments) +{ + String error; + Expression e ("square(x)", error); + TestScope scope; + + EXPECT_TRUE (error.isEmpty()); + EXPECT_EQ (25.0, e.evaluate (scope)); // x=5, so square(5)=25 +} + +// ============================================================================== +// ToString Tests +// ============================================================================== + +TEST (ExpressionTests, ToStringForConstant) +{ + Expression e (42.5); + EXPECT_EQ ("42.5", e.toString()); +} + +TEST (ExpressionTests, ToStringForSymbol) +{ + Expression e = Expression::symbol ("myVar"); + EXPECT_EQ ("myVar", e.toString()); +} + +TEST (ExpressionTests, ToStringForAddition) +{ + Expression e1 (10.0); + Expression e2 (20.0); + Expression result = e1 + e2; + + EXPECT_EQ ("10 + 20", result.toString()); +} + +TEST (ExpressionTests, ToStringForSubtraction) +{ + Expression e1 (50.0); + Expression e2 (30.0); + Expression result = e1 - e2; + + EXPECT_EQ ("50 - 30", result.toString()); +} + +TEST (ExpressionTests, ToStringForMultiplication) +{ + Expression e1 (6.0); + Expression e2 (7.0); + Expression result = e1 * e2; + + EXPECT_EQ ("6 * 7", result.toString()); +} + +TEST (ExpressionTests, ToStringForDivision) +{ + Expression e1 (100.0); + Expression e2 (4.0); + Expression result = e1 / e2; + + EXPECT_EQ ("100 / 4", result.toString()); +} + +TEST (ExpressionTests, ToStringForNegation) +{ + Expression e (42.0); + Expression result = -e; + + EXPECT_EQ ("-42", result.toString()); +} + +TEST (ExpressionTests, ToStringRespectsOperatorPrecedence) +{ + String error; + Expression e ("2 + 3 * 4", error); + + EXPECT_EQ ("2 + 3 * 4", e.toString()); +} + +TEST (ExpressionTests, ToStringWithParentheses) +{ + String error; + Expression e ("(2 + 3) * 4", error); + + EXPECT_EQ ("(2 + 3) * 4", e.toString()); +} + +TEST (ExpressionTests, ToStringForFunction) +{ + Array params; + params.add (Expression (5.0)); + params.add (Expression (10.0)); + + Expression e = Expression::function ("myFunc", params); + EXPECT_EQ ("myFunc (5, 10)", e.toString()); +} + +TEST (ExpressionTests, ToStringForFunctionWithNoParams) +{ + Array params; + Expression e = Expression::function ("myFunc", params); + + EXPECT_EQ ("myFunc()", e.toString()); +} + +// ============================================================================== +// Type and Input Tests +// ============================================================================== + +TEST (ExpressionTests, GetTypeForConstant) +{ + Expression e (42.0); + EXPECT_EQ (Expression::constantType, e.getType()); +} + +TEST (ExpressionTests, GetTypeForSymbol) +{ + Expression e = Expression::symbol ("x"); + EXPECT_EQ (Expression::symbolType, e.getType()); +} + +TEST (ExpressionTests, GetTypeForOperator) +{ + Expression e1 (10.0); + Expression e2 (20.0); + Expression result = e1 + e2; + + EXPECT_EQ (Expression::operatorType, result.getType()); +} + +TEST (ExpressionTests, GetTypeForFunction) +{ + Array params; + params.add (Expression (5.0)); + + Expression e = Expression::function ("func", params); + EXPECT_EQ (Expression::functionType, e.getType()); +} + +TEST (ExpressionTests, GetSymbolOrFunctionForSymbol) +{ + Expression e = Expression::symbol ("mySymbol"); + EXPECT_EQ ("mySymbol", e.getSymbolOrFunction()); +} + +TEST (ExpressionTests, GetSymbolOrFunctionForFunction) +{ + Array params; + Expression e = Expression::function ("myFunc", params); + + EXPECT_EQ ("myFunc", e.getSymbolOrFunction()); +} + +TEST (ExpressionTests, GetSymbolOrFunctionForOperator) +{ + Expression e1 (10.0); + Expression e2 (20.0); + Expression result = e1 + e2; + + EXPECT_EQ ("+", result.getSymbolOrFunction()); +} + +TEST (ExpressionTests, GetNumInputsForConstant) +{ + Expression e (42.0); + EXPECT_EQ (0, e.getNumInputs()); +} + +TEST (ExpressionTests, GetNumInputsForBinaryOperator) +{ + Expression e1 (10.0); + Expression e2 (20.0); + Expression result = e1 + e2; + + EXPECT_EQ (2, result.getNumInputs()); +} + +TEST (ExpressionTests, GetNumInputsForUnaryOperator) +{ + Expression e (42.0); + Expression result = -e; + + EXPECT_EQ (0, result.getNumInputs()); +} + +TEST (ExpressionTests, GetNumInputsForFunction) +{ + Array params; + params.add (Expression (5.0)); + params.add (Expression (10.0)); + params.add (Expression (15.0)); + + Expression e = Expression::function ("func", params); + EXPECT_EQ (3, e.getNumInputs()); +} + +TEST (ExpressionTests, GetInputForBinaryOperator) +{ + Expression e1 (10.0); + Expression e2 (20.0); + Expression result = e1 + e2; + + Expression input0 = result.getInput (0); + Expression input1 = result.getInput (1); + + EXPECT_EQ (10.0, input0.evaluate()); + EXPECT_EQ (20.0, input1.evaluate()); +} + +TEST (ExpressionTests, GetInputForFunction) +{ + Array params; + params.add (Expression (5.0)); + params.add (Expression (10.0)); + + Expression e = Expression::function ("func", params); + + Expression input0 = e.getInput (0); + Expression input1 = e.getInput (1); + + EXPECT_EQ (5.0, input0.evaluate()); + EXPECT_EQ (10.0, input1.evaluate()); +} + +// ============================================================================== +// Parse Static Method Tests +// ============================================================================== + +TEST (ExpressionTests, ParseStaticMethodAdvancesPointer) +{ + String input = "10 + 20, 30"; + auto ptr = input.getCharPointer(); + String error; + + Expression e = Expression::parse (ptr, error); + + EXPECT_TRUE (error.isEmpty()); + EXPECT_EQ (30.0, e.evaluate()); + EXPECT_EQ (' ', *ptr); // Should be pointing at the space before '30' +} + +TEST (ExpressionTests, ParseStaticMethodWithError) +{ + String input = "10 +"; + auto ptr = input.getCharPointer(); + String error; + + Expression e = Expression::parse (ptr, error); + + EXPECT_FALSE (error.isEmpty()); +} + +// ============================================================================== +// AdjustedToGiveNewResult Tests +// ============================================================================== + +TEST (ExpressionTests, AdjustedToGiveNewResultForSimpleAddition) +{ + String error; + Expression e ("x + 10", error); + TestScope scope; + + // x=5, so x+10=15. We want it to equal 20. + Expression adjusted = e.adjustedToGiveNewResult (20.0, scope); + + EXPECT_EQ (20.0, adjusted.evaluate (scope)); +} + +TEST (ExpressionTests, AdjustedToGiveNewResultForConstant) +{ + Expression e (42.0); + Expression::Scope scope; + + Expression adjusted = e.adjustedToGiveNewResult (100.0, scope); + + EXPECT_EQ (100.0, adjusted.evaluate (scope)); +} + +TEST (ExpressionTests, AdjustedToGiveNewResultForMultiplication) +{ + String error; + Expression e ("x * 2", error); + TestScope scope; + + // x=5, so x*2=10. We want it to equal 20. + Expression adjusted = e.adjustedToGiveNewResult (20.0, scope); + + EXPECT_EQ (20.0, adjusted.evaluate (scope)); +} + +TEST (ExpressionTests, AdjustedToGiveNewResultWithResolutionTarget) +{ + String error; + Expression e ("x + @10", error); + TestScope scope; + + // The @10 is a resolution target, so it should be adjusted + Expression adjusted = e.adjustedToGiveNewResult (20.0, scope); + + EXPECT_EQ (20.0, adjusted.evaluate (scope)); +} + +// ============================================================================== +// Dot Operator Tests +// ============================================================================== + +TEST (ExpressionTests, ParseDotOperator) +{ + String error; + Expression e ("inner.value", error); + + EXPECT_TRUE (error.isEmpty()); + EXPECT_TRUE (e.usesAnySymbols()); +} + +TEST (ExpressionTests, EvaluateDotOperator) +{ + String error; + Expression e ("inner.value", error); + OuterScope scope; + + EXPECT_TRUE (error.isEmpty()); + EXPECT_EQ (42.0, e.evaluate (scope)); +} + +TEST (ExpressionTests, ParseThisKeyword) +{ + String error; + Expression e ("this.value", error); + + EXPECT_TRUE (error.isEmpty()); + EXPECT_EQ ("value", e.toString()); +} + +TEST (ExpressionTests, DotOperatorInComplexExpression) +{ + String error; + Expression e ("inner.value * 2", error); + OuterScope scope; + + EXPECT_TRUE (error.isEmpty()); + EXPECT_EQ (84.0, e.evaluate (scope)); +} + +// ============================================================================== +// Edge Cases and Error Handling +// ============================================================================== + +TEST (ExpressionTests, ComplexNestedExpression) +{ + String error; + Expression e ("((10 + 5) * (20 - 8)) / (3 + 1)", error); + + EXPECT_TRUE (error.isEmpty()); + EXPECT_EQ (45.0, e.evaluate()); +} + +TEST (ExpressionTests, ExpressionWithMultipleSpaces) +{ + String error; + Expression e (" 10 + 20 ", error); + + EXPECT_TRUE (error.isEmpty()); + EXPECT_EQ (30.0, e.evaluate()); +} + +TEST (ExpressionTests, ParseMissingClosingParenthesis) +{ + String error; + Expression e ("(10 + 20", error); + + EXPECT_FALSE (error.isEmpty()); +} + +TEST (ExpressionTests, ParseMissingOpeningParenthesis) +{ + String error; + Expression e ("10 + 20)", error); + + EXPECT_FALSE (error.isEmpty()); +} + +TEST (ExpressionTests, ParseIncompleteExpression) +{ + String error; + Expression e ("10 + * 20", error); + + EXPECT_FALSE (error.isEmpty()); +} + +TEST (ExpressionTests, ParseFunctionMissingClosingParenthesis) +{ + String error; + Expression e ("sin(10", error); + + EXPECT_FALSE (error.isEmpty()); +} + +TEST (ExpressionTests, ParseFunctionMissingComma) +{ + String error; + Expression e ("min(10 20)", error); + + EXPECT_FALSE (error.isEmpty()); +} + +TEST (ExpressionTests, ParseTrailingComma) +{ + String error; + Expression e ("min(10, 20,)", error); + + EXPECT_FALSE (error.isEmpty()); +} + +TEST (ExpressionTests, EvaluateWithVeryLargeNumbers) +{ + Expression e1 (1e100); + Expression e2 (1e100); + Expression result = e1 + e2; + + EXPECT_EQ (2e100, result.evaluate()); +} + +TEST (ExpressionTests, EvaluateWithVerySmallNumbers) +{ + Expression e1 (1e-100); + Expression e2 (1e-100); + Expression result = e1 + e2; + + EXPECT_NEAR (2e-100, result.evaluate(), 1e-110); +} + +TEST (ExpressionTests, DoubleNegation) +{ + Expression e (42.0); + Expression result = -(-e); + + EXPECT_EQ (42.0, result.evaluate()); +} + +TEST (ExpressionTests, ChainedNegations) +{ + String error; + Expression e ("---42", error); + + EXPECT_TRUE (error.isEmpty()); + EXPECT_EQ (-42.0, e.evaluate()); +} + +TEST (ExpressionTests, MultipleOperationsWithSamePrecedence) +{ + String error; + Expression e ("10 - 5 - 2", error); + + EXPECT_TRUE (error.isEmpty()); + EXPECT_EQ (3.0, e.evaluate()); +} + +TEST (ExpressionTests, DivisionAndMultiplicationChained) +{ + String error; + Expression e ("100 / 5 * 2", error); + + EXPECT_TRUE (error.isEmpty()); + EXPECT_EQ (40.0, e.evaluate()); +} + +// ============================================================================== +// Symbol Equality Tests +// ============================================================================== + +TEST (ExpressionTests, SymbolEqualityOperator) +{ + Expression::Symbol s1 ("scope1", "symbol1"); + Expression::Symbol s2 ("scope1", "symbol1"); + Expression::Symbol s3 ("scope2", "symbol1"); + Expression::Symbol s4 ("scope1", "symbol2"); + + EXPECT_TRUE (s1 == s2); + EXPECT_FALSE (s1 == s3); + EXPECT_FALSE (s1 == s4); +} + +TEST (ExpressionTests, SymbolInequalityOperator) +{ + Expression::Symbol s1 ("scope1", "symbol1"); + Expression::Symbol s2 ("scope2", "symbol2"); + + EXPECT_TRUE (s1 != s2); + EXPECT_FALSE (s1 != s1); +} + +// ============================================================================== +// Scope Tests +// ============================================================================== + +TEST (ExpressionTests, DefaultScopeHasEmptyUID) +{ + Expression::Scope scope; + EXPECT_TRUE (scope.getScopeUID().isEmpty()); +} + +TEST (ExpressionTests, CustomScopeUID) +{ + TestScope scope; + EXPECT_EQ ("TestScope", scope.getScopeUID()); +} + +TEST (ExpressionTests, ScopeThrowsOnUnknownSymbol) +{ + Expression::Scope scope; + String error; + + Expression e = Expression::symbol ("unknown"); + e.evaluate (scope, error); + + EXPECT_FALSE (error.isEmpty()); +} + +TEST (ExpressionTests, ScopeThrowsOnUnknownFunction) +{ + Expression::Scope scope; + String error; + + Array params; + params.add (Expression (42.0)); + + Expression e = Expression::function ("unknownFunc", params); + e.evaluate (scope, error); + + EXPECT_FALSE (error.isEmpty()); +} + +TEST (ExpressionTests, ScopeThrowsOnUnknownRelativeScope) +{ + Expression::Scope scope; + String error; + + Expression e = Expression::symbol ("unknown"); + e.evaluate (scope, error); + + EXPECT_FALSE (error.isEmpty()); +} + +// ============================================================================== +// Resolution Target Tests +// ============================================================================== + +TEST (ExpressionTests, ParseResolutionTarget) +{ + String error; + Expression e ("@10", error); + + EXPECT_TRUE (error.isEmpty()); + EXPECT_EQ (10.0, e.evaluate()); + EXPECT_EQ ("@10", e.toString()); +} + +TEST (ExpressionTests, ResolutionTargetInExpression) +{ + String error; + Expression e ("x + @5", error); + + EXPECT_TRUE (error.isEmpty()); + EXPECT_EQ ("x + @5", e.toString()); +} + +// ============================================================================== +// Identifier Validation Tests +// ============================================================================== + +TEST (ExpressionTests, ParseIdentifierStartingWithUnderscore) +{ + String error; + Expression e ("_myVar + 10", error); + TestScope scope; + scope.symbols.set ("_myVar", 100.0); + + EXPECT_TRUE (error.isEmpty()); + EXPECT_EQ (110.0, e.evaluate (scope)); +} + +TEST (ExpressionTests, ParseIdentifierWithNumbers) +{ + String error; + Expression e ("var123 + 10", error); + TestScope scope; + scope.symbols.set ("var123", 50.0); + + EXPECT_TRUE (error.isEmpty()); + EXPECT_EQ (60.0, e.evaluate (scope)); +} + +TEST (ExpressionTests, ParseIdentifierWithUnderscores) +{ + String error; + Expression e ("my_var_name + 10", error); + TestScope scope; + scope.symbols.set ("my_var_name", 25.0); + + EXPECT_TRUE (error.isEmpty()); + EXPECT_EQ (35.0, e.evaluate (scope)); +} + +// ============================================================================== +// Comprehensive Integration Tests +// ============================================================================== + +TEST (ExpressionTests, ComplexMathematicalExpression) +{ + String error; + Expression e ("(sin(0) + cos(0)) * (abs(-10) + min(5, 3, 7))", error); + + EXPECT_TRUE (error.isEmpty()); + EXPECT_NEAR (13.0, e.evaluate(), 0.0001); // (0 + 1) * (10 + 3) = 13 +} + +TEST (ExpressionTests, ExpressionWithSymbolsAndFunctions) +{ + String error; + Expression e ("square(x) + square(y)", error); + TestScope scope; + + EXPECT_TRUE (error.isEmpty()); + EXPECT_EQ (125.0, e.evaluate (scope)); // 25 + 100 +} + +TEST (ExpressionTests, DeeplyNestedExpression) +{ + String error; + Expression e ("((((10 + 5) * 2) - 3) / 3)", error); + + EXPECT_TRUE (error.isEmpty()); + EXPECT_EQ (9.0, e.evaluate()); +} + +TEST (ExpressionTests, MixedOperatorsAndFunctions) +{ + String error; + Expression e ("max(10, 20) + min(5, 3) * abs(-2)", error); + + EXPECT_TRUE (error.isEmpty()); + EXPECT_EQ (26.0, e.evaluate()); // 20 + (3 * 2) = 26 +} From 19c1737665045ef26505535c090776359bd22688 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Sun, 7 Dec 2025 10:23:58 +0100 Subject: [PATCH 02/30] Font tests --- modules/yup_graphics/fonts/yup_Font.cpp | 31 +- tests/yup_graphics/yup_Font.cpp | 694 ++++++++++++++++++++++++ 2 files changed, 707 insertions(+), 18 deletions(-) create mode 100644 tests/yup_graphics/yup_Font.cpp diff --git a/modules/yup_graphics/fonts/yup_Font.cpp b/modules/yup_graphics/fonts/yup_Font.cpp index 8590cc19c..a9ee10925 100644 --- a/modules/yup_graphics/fonts/yup_Font.cpp +++ b/modules/yup_graphics/fonts/yup_Font.cpp @@ -28,10 +28,14 @@ namespace uint32_t axisTagFromString (StringRef tagName) { uint32_t tag = 0; - tag += static_cast (tagName[0]) << 24; - tag += static_cast (tagName[1]) << 16; - tag += static_cast (tagName[2]) << 8; - tag += static_cast (tagName[3]) << 0; + if (tagName.length() > 0) + tag += static_cast (tagName[0]) << 24; + if (tagName.length() > 1) + tag += static_cast (tagName[1]) << 16; + if (tagName.length() > 2) + tag += static_cast (tagName[2]) << 8; + if (tagName.length() > 3) + tag += static_cast (tagName[3]) << 0; return tag; } @@ -65,6 +69,9 @@ Font::Font (rive::rcp font, float height) Result Font::loadFromData (const MemoryBlock& fontBytes) { + if (fontBytes.isEmpty()) + return Result::fail ("Unable to instantiate font from empty data"); + font = HBFont::Decode (rive::make_span (static_cast (fontBytes.getData()), fontBytes.getSize())); return font ? Result::ok() : Result::fail ("Unable to load font"); } @@ -161,8 +168,6 @@ std::optional Font::getAxisDescription (int index) const std::optional Font::getAxisDescription (StringRef tagName) const { - jassert (tagName.length() == 4); - if (font == nullptr) return std::nullopt; @@ -221,8 +226,6 @@ void Font::setAxisValue (int index, float value) void Font::setAxisValue (StringRef tagName, float value) { - jassert (tagName.length() == 4); - if (font == nullptr) return; @@ -252,8 +255,6 @@ Font Font::withAxisValue (int index, float value) const Font Font::withAxisValue (StringRef tagName, float value) const { - jassert (tagName.length() == 4); - if (font == nullptr) return {}; @@ -267,9 +268,7 @@ Font Font::withAxisValue (StringRef tagName, float value) const void Font::setAxisValues (std::initializer_list axisOptions) { - jassert (axisOptions.size() > 0); - - if (font == nullptr) + if (font == nullptr || axisOptions.size() == 0) return; std::vector coords; @@ -295,9 +294,7 @@ void Font::setAxisValues (std::initializer_list axisOptions) Font Font::withAxisValues (std::initializer_list axisOptions) const { - jassert (axisOptions.size() > 0); - - if (font == nullptr) + if (font == nullptr || axisOptions.size() == 0) return {}; std::vector coords; @@ -331,8 +328,6 @@ void Font::resetAxisValue (int index) void Font::resetAxisValue (StringRef tagName) { - jassert (tagName.length() == 4); - if (font == nullptr) return; diff --git a/tests/yup_graphics/yup_Font.cpp b/tests/yup_graphics/yup_Font.cpp new file mode 100644 index 000000000..a96b33e96 --- /dev/null +++ b/tests/yup_graphics/yup_Font.cpp @@ -0,0 +1,694 @@ +/* + ============================================================================== + + This file is part of the YUP library. + Copyright (c) 2025 - kunitoki@gmail.com + + YUP is an open source library subject to open-source licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + to use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + YUP IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +#include + +#include + +using namespace yup; + +// ============================================================================== +// Constructor and Assignment Tests +// ============================================================================== + +TEST (FontTests, DefaultConstructorCreatesEmptyFont) +{ + Font font; + + EXPECT_EQ (0.0f, font.getAscent()); + EXPECT_EQ (0.0f, font.getDescent()); + EXPECT_EQ (0, font.getWeight()); + EXPECT_FALSE (font.isItalic()); + EXPECT_EQ (0, font.getNumAxis()); +} + +TEST (FontTests, DefaultConstructorHasDefaultHeight) +{ + Font font; + + EXPECT_EQ (12.0f, font.getHeight()); +} + +TEST (FontTests, CopyConstructor) +{ + Font font1; + font1.setHeight (16.0f); + + Font font2 (font1); + + EXPECT_EQ (font1.getHeight(), font2.getHeight()); + EXPECT_EQ (font1, font2); +} + +TEST (FontTests, MoveConstructor) +{ + Font font1; + font1.setHeight (20.0f); + + Font font2 (std::move (font1)); + + EXPECT_EQ (20.0f, font2.getHeight()); +} + +TEST (FontTests, CopyAssignment) +{ + Font font1; + font1.setHeight (24.0f); + + Font font2; + font2 = font1; + + EXPECT_EQ (font1.getHeight(), font2.getHeight()); + EXPECT_EQ (font1, font2); +} + +TEST (FontTests, MoveAssignment) +{ + Font font1; + font1.setHeight (18.0f); + + Font font2; + font2 = std::move (font1); + + EXPECT_EQ (18.0f, font2.getHeight()); +} + +// ============================================================================== +// Loading Tests +// ============================================================================== + +TEST (FontTests, LoadFromDataWithEmptyData) +{ + Font font; + MemoryBlock emptyData; + + Result result = font.loadFromData (emptyData); + + EXPECT_FALSE (result.wasOk()); + EXPECT_FALSE (result.getErrorMessage().isEmpty()); +} + +TEST (FontTests, DISABLED_LoadFromDataWithInvalidData) // TODO - this doesn't fail harfbuzz!! +{ + Font font; + MemoryBlock invalidData ("invalid font data", 17); + + Result result = font.loadFromData (invalidData); + + EXPECT_FALSE (result.wasOk()); +} + +TEST (FontTests, LoadFromNonExistentFile) +{ + Font font; + File nonExistentFile ("/path/to/nonexistent/font.ttf"); + + Result result = font.loadFromFile (nonExistentFile); + + EXPECT_FALSE (result.wasOk()); + EXPECT_FALSE (result.getErrorMessage().isEmpty()); +} + +TEST (FontTests, LoadFromDirectory) +{ + Font font; + File directory = File::getCurrentWorkingDirectory(); + + Result result = font.loadFromFile (directory); + + EXPECT_FALSE (result.wasOk()); +} + +// ============================================================================== +// Height Tests +// ============================================================================== + +TEST (FontTests, GetHeightReturnsDefaultValue) +{ + Font font; + + EXPECT_EQ (12.0f, font.getHeight()); +} + +TEST (FontTests, SetHeightChangesHeight) +{ + Font font; + font.setHeight (24.0f); + + EXPECT_EQ (24.0f, font.getHeight()); +} + +TEST (FontTests, SetHeightWithZero) +{ + Font font; + font.setHeight (0.0f); + + EXPECT_EQ (0.0f, font.getHeight()); +} + +TEST (FontTests, SetHeightWithNegativeValue) +{ + Font font; + font.setHeight (-10.0f); + + EXPECT_EQ (-10.0f, font.getHeight()); +} + +TEST (FontTests, WithHeightReturnsNewFont) +{ + Font font1; + font1.setHeight (12.0f); + + Font font2 = font1.withHeight (18.0f); + + EXPECT_EQ (12.0f, font1.getHeight()); + EXPECT_EQ (18.0f, font2.getHeight()); +} + +TEST (FontTests, WithHeightDoesNotModifyOriginal) +{ + Font font1; + const float originalHeight = font1.getHeight(); + + Font font2 = font1.withHeight (36.0f); + + EXPECT_EQ (originalHeight, font1.getHeight()); + EXPECT_EQ (36.0f, font2.getHeight()); +} + +// ============================================================================== +// Font Metrics Tests (Empty Font) +// ============================================================================== + +TEST (FontTests, EmptyFontHasZeroAscent) +{ + Font font; + + EXPECT_EQ (0.0f, font.getAscent()); +} + +TEST (FontTests, EmptyFontHasZeroDescent) +{ + Font font; + + EXPECT_EQ (0.0f, font.getDescent()); +} + +TEST (FontTests, EmptyFontHasZeroWeight) +{ + Font font; + + EXPECT_EQ (0, font.getWeight()); +} + +TEST (FontTests, EmptyFontIsNotItalic) +{ + Font font; + + EXPECT_FALSE (font.isItalic()); +} + +// ============================================================================== +// Axis Tests (Empty Font) +// ============================================================================== + +TEST (FontTests, EmptyFontHasNoAxis) +{ + Font font; + + EXPECT_EQ (0, font.getNumAxis()); +} + +TEST (FontTests, GetAxisDescriptionByIndexReturnsNulloptForEmptyFont) +{ + Font font; + + auto axis = font.getAxisDescription (0); + + EXPECT_FALSE (axis.has_value()); +} + +TEST (FontTests, GetAxisDescriptionByTagReturnsNulloptForEmptyFont) +{ + Font font; + + auto axis = font.getAxisDescription ("wght"); + + EXPECT_FALSE (axis.has_value()); +} + +TEST (FontTests, GetAxisValueByIndexReturnsZeroForEmptyFont) +{ + Font font; + + EXPECT_EQ (0.0f, font.getAxisValue (0)); +} + +TEST (FontTests, GetAxisValueByInvalidIndex) +{ + Font font; + + EXPECT_EQ (0.0f, font.getAxisValue (-1)); + EXPECT_EQ (0.0f, font.getAxisValue (100)); +} + +TEST (FontTests, GetAxisValueByTagReturnsZeroForEmptyFont) +{ + Font font; + + EXPECT_EQ (0.0f, font.getAxisValue ("wght")); +} + +TEST (FontTests, SetAxisValueByIndexDoesNothingForEmptyFont) +{ + Font font; + + // Should not crash + font.setAxisValue (0, 500.0f); + + EXPECT_EQ (0, font.getNumAxis()); +} + +TEST (FontTests, SetAxisValueByInvalidIndex) +{ + Font font; + + // Should not crash + font.setAxisValue (-1, 500.0f); + font.setAxisValue (100, 500.0f); + + EXPECT_EQ (0, font.getNumAxis()); +} + +TEST (FontTests, SetAxisValueByTagDoesNothingForEmptyFont) +{ + Font font; + + // Should not crash + font.setAxisValue ("wght", 700.0f); + + EXPECT_EQ (0, font.getNumAxis()); +} + +TEST (FontTests, WithAxisValueByIndexReturnsEmptyFontForEmptyFont) +{ + Font font; + + Font newFont = font.withAxisValue (0, 600.0f); + + EXPECT_EQ (0, newFont.getNumAxis()); +} + +TEST (FontTests, WithAxisValueByTagReturnsEmptyFontForEmptyFont) +{ + Font font; + + Font newFont = font.withAxisValue ("wght", 700.0f); + + EXPECT_EQ (0, newFont.getNumAxis()); +} + +TEST (FontTests, ResetAxisValueByIndexDoesNothingForEmptyFont) +{ + Font font; + + // Should not crash + font.resetAxisValue (0); + + EXPECT_EQ (0, font.getNumAxis()); +} + +TEST (FontTests, ResetAxisValueByTagDoesNothingForEmptyFont) +{ + Font font; + + // Should not crash + font.resetAxisValue ("wght"); + + EXPECT_EQ (0, font.getNumAxis()); +} + +TEST (FontTests, ResetAllAxisValuesDoesNothingForEmptyFont) +{ + Font font; + + // Should not crash + font.resetAllAxisValues(); + + EXPECT_EQ (0, font.getNumAxis()); +} + +TEST (FontTests, SetAxisValuesDoesNothingForEmptyFont) +{ + Font font; + + // Should not crash + font.setAxisValues ({ { "wght", 700.0f }, { "wdth", 75.0f } }); + + EXPECT_EQ (0, font.getNumAxis()); +} + +TEST (FontTests, WithAxisValuesReturnsEmptyFontForEmptyFont) +{ + Font font; + + Font newFont = font.withAxisValues ({ { "wght", 700.0f }, { "wdth", 75.0f } }); + + EXPECT_EQ (0, newFont.getNumAxis()); +} + +// ============================================================================== +// Feature Tests +// ============================================================================== + +TEST (FontTests, FeatureConstructorWithTag) +{ + Font::Feature feature (0x6C696761, 1); // 'liga' + + EXPECT_EQ (0x6C696761u, feature.tag); + EXPECT_EQ (1u, feature.value); +} + +TEST (FontTests, FeatureConstructorWithString) +{ + Font::Feature feature ("liga", 1); + + EXPECT_EQ (0x6C696761u, feature.tag); // 'liga' in hex + EXPECT_EQ (1u, feature.value); +} + +TEST (FontTests, FeatureConstructorWithDifferentStrings) +{ + Font::Feature kern ("kern", 0); + Font::Feature smcp ("smcp", 1); + + EXPECT_NE (kern.tag, smcp.tag); +} + +TEST (FontTests, WithFeatureReturnsEmptyFontForEmptyFont) +{ + Font font; + Font::Feature feature ("liga", 1); + + Font newFont = font.withFeature (feature); + + EXPECT_EQ (0, newFont.getNumAxis()); +} + +TEST (FontTests, WithFeaturesReturnsEmptyFontForEmptyFont) +{ + Font font; + + Font newFont = font.withFeatures ({ { "liga", 1 }, + { "kern", 1 }, + { "smcp", 1 } }); + + EXPECT_EQ (0, newFont.getNumAxis()); +} + +TEST (FontTests, DISABLED_FeatureStringTagMustBe4Characters) +{ + // This would trigger the assertion in debug builds + // but should not crash in release + Font::Feature feature1 ("abc", 1); // Too short + Font::Feature feature2 ("abcde", 1); // Too long (will only use first 4) + + // At least verify they construct + EXPECT_EQ (1u, feature1.value); + EXPECT_EQ (1u, feature2.value); +} + +// ============================================================================== +// Axis Option Tests +// ============================================================================== + +TEST (FontTests, AxisOptionConstructor) +{ + Font::AxisOption option ("wght", 700.0f); + + EXPECT_EQ ("wght", option.tagName); + EXPECT_EQ (700.0f, option.value); +} + +TEST (FontTests, AxisOptionWithDifferentValues) +{ + Font::AxisOption weight ("wght", 400.0f); + Font::AxisOption width ("wdth", 75.0f); + + EXPECT_EQ ("wght", weight.tagName); + EXPECT_EQ (400.0f, weight.value); + EXPECT_EQ ("wdth", width.tagName); + EXPECT_EQ (75.0f, width.value); +} + +// ============================================================================== +// Axis Description Tests +// ============================================================================== + +TEST (FontTests, AxisDefaultConstructor) +{ + Font::Axis axis; + + EXPECT_TRUE (axis.tagName.isEmpty()); + EXPECT_EQ (0.0f, axis.minimumValue); + EXPECT_EQ (0.0f, axis.maximumValue); + EXPECT_EQ (0.0f, axis.defaultValue); +} + +// ============================================================================== +// Equality Tests +// ============================================================================== + +TEST (FontTests, EmptyFontsAreEqual) +{ + Font font1; + Font font2; + + EXPECT_TRUE (font1 == font2); + EXPECT_FALSE (font1 != font2); +} + +TEST (FontTests, SameFontsAreEqual) +{ + Font font1; + Font font2 = font1; + + EXPECT_TRUE (font1 == font2); + EXPECT_FALSE (font1 != font2); +} + +TEST (FontTests, HeightDoesNotAffectEquality) +{ + Font font1; + font1.setHeight (12.0f); + + Font font2; + font2.setHeight (24.0f); + + // Fonts are equal if they wrap the same underlying rive::Font + EXPECT_TRUE (font1 == font2); +} + +TEST (FontTests, InequalityOperator) +{ + Font font1; + Font font2; + + // Both empty fonts should be equal + EXPECT_FALSE (font1 != font2); + EXPECT_TRUE (font1 == font2); +} + +// ============================================================================== +// Chain Operations Tests +// ============================================================================== + +TEST (FontTests, ChainWithHeightOperations) +{ + Font font; + + Font newFont = font.withHeight (16.0f).withHeight (24.0f); + + EXPECT_EQ (24.0f, newFont.getHeight()); + EXPECT_EQ (12.0f, font.getHeight()); +} + +TEST (FontTests, CombinedHeightAndAxisOperations) +{ + Font font; + + Font newFont = font + .withHeight (18.0f) + .withAxisValue ("wght", 700.0f) + .withHeight (24.0f); + + EXPECT_EQ (24.0f, newFont.getHeight()); + EXPECT_EQ (12.0f, font.getHeight()); +} + +// ============================================================================== +// Edge Cases +// ============================================================================== + +TEST (FontTests, SetHeightWithVeryLargeValue) +{ + Font font; + font.setHeight (10000.0f); + + EXPECT_EQ (10000.0f, font.getHeight()); +} + +TEST (FontTests, SetHeightWithVerySmallValue) +{ + Font font; + font.setHeight (0.001f); + + EXPECT_EQ (0.001f, font.getHeight()); +} + +TEST (FontTests, MultipleHeightChanges) +{ + Font font; + + font.setHeight (16.0f); + EXPECT_EQ (16.0f, font.getHeight()); + + font.setHeight (20.0f); + EXPECT_EQ (20.0f, font.getHeight()); + + font.setHeight (12.0f); + EXPECT_EQ (12.0f, font.getHeight()); +} + +TEST (FontTests, CopyFontPreservesHeight) +{ + Font font1; + font1.setHeight (32.0f); + + Font font2 = font1; + font2.setHeight (48.0f); + + EXPECT_EQ (32.0f, font1.getHeight()); + EXPECT_EQ (48.0f, font2.getHeight()); +} + +TEST (FontTests, WithAxisValuesEmptyList) +{ + Font font; + + // Empty initializer list should handle gracefully + Font newFont = font.withAxisValues ({}); + + EXPECT_EQ (0, newFont.getNumAxis()); +} + +TEST (FontTests, SetAxisValuesEmptyList) +{ + Font font; + + // Empty initializer list should handle gracefully + font.setAxisValues ({}); + + EXPECT_EQ (0, font.getNumAxis()); +} + +TEST (FontTests, GetAxisDescriptionOutOfBounds) +{ + Font font; + + EXPECT_FALSE (font.getAxisDescription (-1).has_value()); + EXPECT_FALSE (font.getAxisDescription (0).has_value()); + EXPECT_FALSE (font.getAxisDescription (1000).has_value()); +} + +TEST (FontTests, AxisTagNameMustBe4Characters) +{ + Font font; + + // These should handle gracefully (won't find the axis) + EXPECT_FALSE (font.getAxisDescription ("w").has_value()); + EXPECT_FALSE (font.getAxisDescription ("wg").has_value()); + EXPECT_FALSE (font.getAxisDescription ("wgh").has_value()); + EXPECT_FALSE (font.getAxisDescription ("wghtt").has_value()); +} + +// ============================================================================== +// Memory and Resource Tests +// ============================================================================== + +TEST (FontTests, CopyDoesNotCrash) +{ + Font font1; + font1.setHeight (16.0f); + + { + Font font2 = font1; + EXPECT_EQ (font1.getHeight(), font2.getHeight()); + } + + // font1 should still be valid + EXPECT_EQ (16.0f, font1.getHeight()); +} + +TEST (FontTests, MoveDoesNotCrash) +{ + Font font1; + font1.setHeight (20.0f); + + { + Font font2 = std::move (font1); + EXPECT_EQ (20.0f, font2.getHeight()); + } + + // Moved-from font is in valid but unspecified state + // Should not crash when destroyed +} + +TEST (FontTests, MultipleOperationsOnSameFont) +{ + Font font; + + font.setHeight (16.0f); + font.setAxisValue (0, 700.0f); + font.resetAxisValue (0); + font.resetAllAxisValues(); + font.setAxisValues ({ { "wght", 400.0f } }); + + // Should not crash + EXPECT_EQ (16.0f, font.getHeight()); +} + +TEST (FontTests, ChainedWithOperations) +{ + Font font; + + Font result = font + .withHeight (16.0f) + .withAxisValue (0, 700.0f) + .withAxisValue ("wdth", 75.0f) + .withAxisValues ({ { "wght", 400.0f } }) + .withFeature ({ "liga", 1 }) + .withFeatures ({ { "kern", 1 }, { "smcp", 1 } }) + .withHeight (24.0f); + + EXPECT_EQ (24.0f, result.getHeight()); + EXPECT_EQ (12.0f, font.getHeight()); // Original unchanged +} From 668681c7161481e90698b0ec0e33f6a1e4347d8a Mon Sep 17 00:00:00 2001 From: kunitoki Date: Sun, 7 Dec 2025 10:35:11 +0100 Subject: [PATCH 03/30] More tests --- modules/yup_graphics/fonts/yup_Font.cpp | 2 +- tests/yup_graphics/yup_StyledText.cpp | 965 ++++++++++++++++++++++++ 2 files changed, 966 insertions(+), 1 deletion(-) create mode 100644 tests/yup_graphics/yup_StyledText.cpp diff --git a/modules/yup_graphics/fonts/yup_Font.cpp b/modules/yup_graphics/fonts/yup_Font.cpp index a9ee10925..f6cf5ed2f 100644 --- a/modules/yup_graphics/fonts/yup_Font.cpp +++ b/modules/yup_graphics/fonts/yup_Font.cpp @@ -294,7 +294,7 @@ void Font::setAxisValues (std::initializer_list axisOptions) Font Font::withAxisValues (std::initializer_list axisOptions) const { - if (font == nullptr || axisOptions.size() == 0) + if (font == nullptr || axisOptions.size() == 0) return {}; std::vector coords; diff --git a/tests/yup_graphics/yup_StyledText.cpp b/tests/yup_graphics/yup_StyledText.cpp new file mode 100644 index 000000000..0092e826e --- /dev/null +++ b/tests/yup_graphics/yup_StyledText.cpp @@ -0,0 +1,965 @@ +/* + ============================================================================== + + This file is part of the YUP library. + Copyright (c) 2025 - kunitoki@gmail.com + + YUP is an open source library subject to open-source licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + to use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + YUP IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +#include + +#include + +using namespace yup; + +// ============================================================================== +// Default Constructor and State Tests +// ============================================================================== + +TEST (StyledTextTests, DefaultConstructorCreatesEmptyText) +{ + StyledText text; + + EXPECT_TRUE (text.isEmpty()); + EXPECT_FALSE (text.needsUpdate()); +} + +TEST (StyledTextTests, DefaultOverflowIsVisible) +{ + StyledText text; + + EXPECT_EQ (StyledText::visible, text.getOverflow()); +} + +TEST (StyledTextTests, DefaultHorizontalAlignIsLeft) +{ + StyledText text; + + EXPECT_EQ (StyledText::left, text.getHorizontalAlign()); +} + +TEST (StyledTextTests, DefaultVerticalAlignIsTop) +{ + StyledText text; + + EXPECT_EQ (StyledText::top, text.getVerticalAlign()); +} + +TEST (StyledTextTests, DefaultMaxSizeIsUnlimited) +{ + StyledText text; + Size maxSize = text.getMaxSize(); + + EXPECT_FLOAT_EQ (-1.0f, maxSize.getWidth()); + EXPECT_FLOAT_EQ (-1.0f, maxSize.getHeight()); +} + +TEST (StyledTextTests, DefaultParagraphSpacingIsZero) +{ + StyledText text; + + EXPECT_FLOAT_EQ (0.0f, text.getParagraphSpacing()); +} + +TEST (StyledTextTests, DefaultWrapIsWrap) +{ + StyledText text; + + EXPECT_EQ (StyledText::wrap, text.getWrap()); +} + +TEST (StyledTextTests, DefaultComputedBoundsIsEmpty) +{ + StyledText text; + Rectangle bounds = text.getComputedTextBounds(); + + EXPECT_FLOAT_EQ (0.0f, bounds.getWidth()); + EXPECT_FLOAT_EQ (0.0f, bounds.getHeight()); +} + +// ============================================================================== +// Overflow Tests +// ============================================================================== + +TEST (StyledTextTests, SetOverflowToEllipsis) +{ + StyledText text; + + { + auto modifier = text.startUpdate(); + modifier.setOverflow (StyledText::ellipsis); + } + + EXPECT_EQ (StyledText::ellipsis, text.getOverflow()); +} + +TEST (StyledTextTests, SetOverflowToVisible) +{ + StyledText text; + + { + auto modifier = text.startUpdate(); + modifier.setOverflow (StyledText::visible); + } + + EXPECT_EQ (StyledText::visible, text.getOverflow()); +} + +TEST (StyledTextTests, SetOverflowMultipleTimes) +{ + StyledText text; + + { + auto modifier = text.startUpdate(); + modifier.setOverflow (StyledText::ellipsis); + } + + EXPECT_EQ (StyledText::ellipsis, text.getOverflow()); + + { + auto modifier = text.startUpdate(); + modifier.setOverflow (StyledText::visible); + } + + EXPECT_EQ (StyledText::visible, text.getOverflow()); +} + +// ============================================================================== +// Max Size Tests +// ============================================================================== + +TEST (StyledTextTests, SetMaxSize) +{ + StyledText text; + + { + auto modifier = text.startUpdate(); + modifier.setMaxSize (Size (200.0f, 100.0f)); + } + + Size maxSize = text.getMaxSize(); + EXPECT_FLOAT_EQ (200.0f, maxSize.getWidth()); + EXPECT_FLOAT_EQ (100.0f, maxSize.getHeight()); +} + +TEST (StyledTextTests, SetMaxSizeToZero) +{ + StyledText text; + + { + auto modifier = text.startUpdate(); + modifier.setMaxSize (Size (0.0f, 0.0f)); + } + + Size maxSize = text.getMaxSize(); + EXPECT_FLOAT_EQ (0.0f, maxSize.getWidth()); + EXPECT_FLOAT_EQ (0.0f, maxSize.getHeight()); +} + +TEST (StyledTextTests, SetMaxSizeToLargeValues) +{ + StyledText text; + + { + auto modifier = text.startUpdate(); + modifier.setMaxSize (Size (10000.0f, 5000.0f)); + } + + Size maxSize = text.getMaxSize(); + EXPECT_FLOAT_EQ (10000.0f, maxSize.getWidth()); + EXPECT_FLOAT_EQ (5000.0f, maxSize.getHeight()); +} + +// ============================================================================== +// Paragraph Spacing Tests +// ============================================================================== + +TEST (StyledTextTests, SetParagraphSpacing) +{ + StyledText text; + + { + auto modifier = text.startUpdate(); + modifier.setParagraphSpacing (10.0f); + } + + EXPECT_FLOAT_EQ (10.0f, text.getParagraphSpacing()); +} + +TEST (StyledTextTests, SetParagraphSpacingToZero) +{ + StyledText text; + + { + auto modifier = text.startUpdate(); + modifier.setParagraphSpacing (0.0f); + } + + EXPECT_FLOAT_EQ (0.0f, text.getParagraphSpacing()); +} + +TEST (StyledTextTests, SetParagraphSpacingToNegativeValue) +{ + StyledText text; + + { + auto modifier = text.startUpdate(); + modifier.setParagraphSpacing (-5.0f); + } + + EXPECT_FLOAT_EQ (-5.0f, text.getParagraphSpacing()); +} + +TEST (StyledTextTests, SetParagraphSpacingMultipleTimes) +{ + StyledText text; + + { + auto modifier = text.startUpdate(); + modifier.setParagraphSpacing (10.0f); + } + + EXPECT_FLOAT_EQ (10.0f, text.getParagraphSpacing()); + + { + auto modifier = text.startUpdate(); + modifier.setParagraphSpacing (20.0f); + } + + EXPECT_FLOAT_EQ (20.0f, text.getParagraphSpacing()); +} + +// ============================================================================== +// Wrap Tests +// ============================================================================== + +TEST (StyledTextTests, SetWrapToNoWrap) +{ + StyledText text; + + { + auto modifier = text.startUpdate(); + modifier.setWrap (StyledText::noWrap); + } + + EXPECT_EQ (StyledText::noWrap, text.getWrap()); +} + +TEST (StyledTextTests, SetWrapToWrap) +{ + StyledText text; + + { + auto modifier = text.startUpdate(); + modifier.setWrap (StyledText::wrap); + } + + EXPECT_EQ (StyledText::wrap, text.getWrap()); +} + +TEST (StyledTextTests, SetWrapMultipleTimes) +{ + StyledText text; + + { + auto modifier = text.startUpdate(); + modifier.setWrap (StyledText::noWrap); + } + + EXPECT_EQ (StyledText::noWrap, text.getWrap()); + + { + auto modifier = text.startUpdate(); + modifier.setWrap (StyledText::wrap); + } + + EXPECT_EQ (StyledText::wrap, text.getWrap()); +} + +// ============================================================================== +// Justification Conversion Tests +// ============================================================================== + +TEST (StyledTextTests, HorizontalAlignFromJustificationLeft) +{ + auto align = StyledText::horizontalAlignFromJustification (Justification::left); + + EXPECT_EQ (StyledText::left, align); +} + +TEST (StyledTextTests, HorizontalAlignFromJustificationCenter) +{ + auto align = StyledText::horizontalAlignFromJustification (Justification::horizontalCenter); + + EXPECT_EQ (StyledText::center, align); +} + +TEST (StyledTextTests, HorizontalAlignFromJustificationRight) +{ + auto align = StyledText::horizontalAlignFromJustification (Justification::right); + + EXPECT_EQ (StyledText::right, align); +} + +TEST (StyledTextTests, HorizontalAlignFromJustificationCentered) +{ + auto align = StyledText::horizontalAlignFromJustification (Justification::center); + + EXPECT_EQ (StyledText::center, align); +} + +TEST (StyledTextTests, HorizontalAlignFromJustificationCenteredLeft) +{ + auto align = StyledText::horizontalAlignFromJustification (Justification::centerLeft); + + EXPECT_EQ (StyledText::left, align); +} + +TEST (StyledTextTests, HorizontalAlignFromJustificationCenteredRight) +{ + auto align = StyledText::horizontalAlignFromJustification (Justification::centerRight); + + EXPECT_EQ (StyledText::right, align); +} + +TEST (StyledTextTests, VerticalAlignFromJustificationTop) +{ + auto align = StyledText::verticalAlignFromJustification (Justification::top); + + EXPECT_EQ (StyledText::top, align); +} + +TEST (StyledTextTests, VerticalAlignFromJustificationMiddle) +{ + auto align = StyledText::verticalAlignFromJustification (Justification::verticalCenter); + + EXPECT_EQ (StyledText::middle, align); +} + +TEST (StyledTextTests, VerticalAlignFromJustificationBottom) +{ + auto align = StyledText::verticalAlignFromJustification (Justification::bottom); + + EXPECT_EQ (StyledText::bottom, align); +} + +TEST (StyledTextTests, VerticalAlignFromJustificationCentered) +{ + auto align = StyledText::verticalAlignFromJustification (Justification::center); + + EXPECT_EQ (StyledText::middle, align); +} + +TEST (StyledTextTests, VerticalAlignFromJustificationCenteredTop) +{ + auto align = StyledText::verticalAlignFromJustification (Justification::centerTop); + + EXPECT_EQ (StyledText::top, align); +} + +TEST (StyledTextTests, VerticalAlignFromJustificationCenteredBottom) +{ + auto align = StyledText::verticalAlignFromJustification (Justification::centerBottom); + + EXPECT_EQ (StyledText::bottom, align); +} + +// ============================================================================== +// Empty Text State Tests +// ============================================================================== + +TEST (StyledTextTests, GetGlyphIndexAtPositionReturnsZeroForEmptyText) +{ + StyledText text; + + int index = text.getGlyphIndexAtPosition (Point (10.0f, 10.0f)); + + EXPECT_EQ (0, index); +} + +TEST (StyledTextTests, GetCaretBoundsReturnsEmptyForEmptyText) +{ + StyledText text; + + Rectangle bounds = text.getCaretBounds (0); + + EXPECT_FLOAT_EQ (0.0f, bounds.getWidth()); + EXPECT_FLOAT_EQ (0.0f, bounds.getHeight()); +} + +TEST (StyledTextTests, GetSelectionRectanglesReturnsEmptyForEmptyText) +{ + StyledText text; + + auto rectangles = text.getSelectionRectangles (0, 5); + + EXPECT_TRUE (rectangles.empty()); +} + +TEST (StyledTextTests, GetSelectionRectanglesReturnsEmptyForInvalidRange) +{ + StyledText text; + + auto rectangles = text.getSelectionRectangles (5, 0); + + EXPECT_TRUE (rectangles.empty()); +} + +TEST (StyledTextTests, GetSelectionRectanglesReturnsEmptyForNegativeIndices) +{ + StyledText text; + + auto rectangles = text.getSelectionRectangles (-1, -5); + + EXPECT_TRUE (rectangles.empty()); +} + +TEST (StyledTextTests, GetSelectionRectanglesReturnsEmptyForEqualIndices) +{ + StyledText text; + + auto rectangles = text.getSelectionRectangles (5, 5); + + EXPECT_TRUE (rectangles.empty()); +} + +TEST (StyledTextTests, GetOrderedLinesReturnsEmptyForEmptyText) +{ + StyledText text; + + auto lines = text.getOrderedLines(); + + EXPECT_TRUE (lines.empty()); +} + +TEST (StyledTextTests, GetRenderStylesReturnsEmptyForEmptyText) +{ + StyledText text; + + auto styles = text.getRenderStyles(); + + EXPECT_TRUE (styles.empty()); +} + +TEST (StyledTextTests, IsValidCharacterIndexReturnsTrueForZeroOnEmptyText) +{ + StyledText text; + + EXPECT_TRUE (text.isValidCharacterIndex (0)); +} + +TEST (StyledTextTests, IsValidCharacterIndexReturnsFalseForNegativeIndex) +{ + StyledText text; + + EXPECT_FALSE (text.isValidCharacterIndex (-1)); +} + +TEST (StyledTextTests, IsValidCharacterIndexReturnsFalseForLargeIndex) +{ + StyledText text; + + EXPECT_FALSE (text.isValidCharacterIndex (1000)); +} + +// ============================================================================== +// Offset Tests +// ============================================================================== + +TEST (StyledTextTests, GetOffsetWithLeftTopAlignment) +{ + StyledText text; + + { + auto modifier = text.startUpdate(); + modifier.setHorizontalAlign (StyledText::left); + modifier.setVerticalAlign (StyledText::top); + } + + Point offset = text.getOffset (Rectangle (0.0f, 0.0f, 200.0f, 100.0f)); + + EXPECT_FLOAT_EQ (0.0f, offset.getX()); + EXPECT_FLOAT_EQ (0.0f, offset.getY()); +} + +TEST (StyledTextTests, GetOffsetWithCenterMiddleAlignment) +{ + StyledText text; + + { + auto modifier = text.startUpdate(); + modifier.setHorizontalAlign (StyledText::center); + modifier.setVerticalAlign (StyledText::middle); + } + + Rectangle area (0.0f, 0.0f, 200.0f, 100.0f); + Point offset = text.getOffset (area); + + // Empty text has 0 bounds, so offset should center the empty bounds + EXPECT_FLOAT_EQ (100.0f, offset.getX()); // (200 - 0) * 0.5 + EXPECT_FLOAT_EQ (50.0f, offset.getY()); // (100 - 0) * 0.5 +} + +TEST (StyledTextTests, GetOffsetWithRightBottomAlignment) +{ + StyledText text; + + { + auto modifier = text.startUpdate(); + modifier.setHorizontalAlign (StyledText::right); + modifier.setVerticalAlign (StyledText::bottom); + } + + Rectangle area (0.0f, 0.0f, 200.0f, 100.0f); + Point offset = text.getOffset (area); + + // Empty text has 0 bounds + EXPECT_FLOAT_EQ (200.0f, offset.getX()); // 200 - 0 + EXPECT_FLOAT_EQ (100.0f, offset.getY()); // 100 - 0 +} + +TEST (StyledTextTests, GetOffsetWithJustifiedAlignment) +{ + StyledText text; + + { + auto modifier = text.startUpdate(); + modifier.setHorizontalAlign (StyledText::justified); + modifier.setVerticalAlign (StyledText::middle); + } + + Rectangle area (0.0f, 0.0f, 200.0f, 100.0f); + Point offset = text.getOffset (area); + + // Justified is treated as left for horizontal alignment + EXPECT_FLOAT_EQ (0.0f, offset.getX()); + EXPECT_FLOAT_EQ (50.0f, offset.getY()); +} + +TEST (StyledTextTests, GetOffsetWithZeroArea) +{ + StyledText text; + + { + auto modifier = text.startUpdate(); + modifier.setHorizontalAlign (StyledText::center); + modifier.setVerticalAlign (StyledText::middle); + } + + Point offset = text.getOffset (Rectangle (0.0f, 0.0f, 0.0f, 0.0f)); + + EXPECT_FLOAT_EQ (0.0f, offset.getX()); + EXPECT_FLOAT_EQ (0.0f, offset.getY()); +} + +TEST (StyledTextTests, GetOffsetWithLargeArea) +{ + StyledText text; + + { + auto modifier = text.startUpdate(); + modifier.setHorizontalAlign (StyledText::center); + modifier.setVerticalAlign (StyledText::middle); + } + + Rectangle area (0.0f, 0.0f, 10000.0f, 5000.0f); + Point offset = text.getOffset (area); + + EXPECT_FLOAT_EQ (5000.0f, offset.getX()); + EXPECT_FLOAT_EQ (2500.0f, offset.getY()); +} + +// ============================================================================== +// TextModifier Tests +// ============================================================================== + +TEST (StyledTextTests, TextModifierClearMakesTextEmpty) +{ + StyledText text; + + { + auto modifier = text.startUpdate(); + modifier.clear(); + } + + EXPECT_TRUE (text.isEmpty()); +} + +TEST (StyledTextTests, TextModifierMultiplePropertiesInSingleUpdate) +{ + StyledText text; + + { + auto modifier = text.startUpdate(); + modifier.setOverflow (StyledText::ellipsis); + modifier.setHorizontalAlign (StyledText::center); + modifier.setVerticalAlign (StyledText::middle); + modifier.setMaxSize (Size (300.0f, 200.0f)); + modifier.setParagraphSpacing (15.0f); + modifier.setWrap (StyledText::noWrap); + } + + EXPECT_EQ (StyledText::ellipsis, text.getOverflow()); + EXPECT_EQ (StyledText::center, text.getHorizontalAlign()); + EXPECT_EQ (StyledText::middle, text.getVerticalAlign()); + EXPECT_EQ (Size (300.0f, 200.0f), text.getMaxSize()); + EXPECT_FLOAT_EQ (15.0f, text.getParagraphSpacing()); + EXPECT_EQ (StyledText::noWrap, text.getWrap()); +} + +TEST (StyledTextTests, TextModifierDestructorTriggersUpdate) +{ + StyledText text; + + EXPECT_FALSE (text.needsUpdate()); + + { + auto modifier = text.startUpdate(); + modifier.setOverflow (StyledText::ellipsis); + // Update happens when modifier goes out of scope + } + + // After modifier destruction, update should have been called + EXPECT_FALSE (text.needsUpdate()); +} + +TEST (StyledTextTests, MultipleTextModifierScopes) +{ + StyledText text; + + { + auto modifier = text.startUpdate(); + modifier.setParagraphSpacing (10.0f); + } + + EXPECT_FLOAT_EQ (10.0f, text.getParagraphSpacing()); + + { + auto modifier = text.startUpdate(); + modifier.setParagraphSpacing (20.0f); + } + + EXPECT_FLOAT_EQ (20.0f, text.getParagraphSpacing()); +} + +// ============================================================================== +// Combined Property Tests +// ============================================================================== + +TEST (StyledTextTests, SetAllPropertiesSequentially) +{ + StyledText text; + + { + auto modifier = text.startUpdate(); + modifier.setOverflow (StyledText::ellipsis); + } + + { + auto modifier = text.startUpdate(); + modifier.setHorizontalAlign (StyledText::center); + } + + { + auto modifier = text.startUpdate(); + modifier.setVerticalAlign (StyledText::bottom); + } + + { + auto modifier = text.startUpdate(); + modifier.setMaxSize (Size (400.0f, 300.0f)); + } + + { + auto modifier = text.startUpdate(); + modifier.setParagraphSpacing (25.0f); + } + + { + auto modifier = text.startUpdate(); + modifier.setWrap (StyledText::noWrap); + } + + EXPECT_EQ (StyledText::ellipsis, text.getOverflow()); + EXPECT_EQ (StyledText::center, text.getHorizontalAlign()); + EXPECT_EQ (StyledText::bottom, text.getVerticalAlign()); + EXPECT_EQ (Size (400.0f, 300.0f), text.getMaxSize()); + EXPECT_FLOAT_EQ (25.0f, text.getParagraphSpacing()); + EXPECT_EQ (StyledText::noWrap, text.getWrap()); +} + +TEST (StyledTextTests, PropertyChangesDoNotAffectOtherProperties) +{ + StyledText text; + + { + auto modifier = text.startUpdate(); + modifier.setOverflow (StyledText::ellipsis); + modifier.setParagraphSpacing (10.0f); + } + + EXPECT_EQ (StyledText::ellipsis, text.getOverflow()); + EXPECT_FLOAT_EQ (10.0f, text.getParagraphSpacing()); + + { + auto modifier = text.startUpdate(); + modifier.setOverflow (StyledText::visible); + } + + EXPECT_EQ (StyledText::visible, text.getOverflow()); + EXPECT_FLOAT_EQ (10.0f, text.getParagraphSpacing()); // Should remain unchanged +} + +// ============================================================================== +// Edge Cases +// ============================================================================== + +TEST (StyledTextTests, GetCaretBoundsWithNegativeIndex) +{ + StyledText text; + + Rectangle bounds = text.getCaretBounds (-1); + + // Should handle gracefully (likely returns empty or clamped to 0) + EXPECT_GE (bounds.getX(), 0.0f); +} + +TEST (StyledTextTests, GetCaretBoundsWithLargeIndex) +{ + StyledText text; + + Rectangle bounds = text.getCaretBounds (10000); + + // Should handle gracefully + EXPECT_TRUE (bounds.isEmpty() || bounds.getWidth() >= 0.0f); +} + +TEST (StyledTextTests, GetGlyphIndexAtNegativePosition) +{ + StyledText text; + + int index = text.getGlyphIndexAtPosition (Point (-100.0f, -100.0f)); + + EXPECT_GE (index, 0); +} + +TEST (StyledTextTests, GetGlyphIndexAtVeryLargePosition) +{ + StyledText text; + + int index = text.getGlyphIndexAtPosition (Point (10000.0f, 10000.0f)); + + EXPECT_GE (index, 0); +} + +TEST (StyledTextTests, GetOffsetWithNegativeArea) +{ + StyledText text; + + { + auto modifier = text.startUpdate(); + modifier.setHorizontalAlign (StyledText::center); + modifier.setVerticalAlign (StyledText::middle); + } + + // Negative dimensions should still compute offset + Point offset = text.getOffset (Rectangle (0.0f, 0.0f, -100.0f, -50.0f)); + + // Implementation should handle this gracefully + EXPECT_TRUE (std::isfinite (offset.getX())); + EXPECT_TRUE (std::isfinite (offset.getY())); +} + +TEST (StyledTextTests, SetSamePropertyValueMultipleTimes) +{ + StyledText text; + + { + auto modifier = text.startUpdate(); + modifier.setParagraphSpacing (10.0f); + } + + EXPECT_FLOAT_EQ (10.0f, text.getParagraphSpacing()); + + { + auto modifier = text.startUpdate(); + modifier.setParagraphSpacing (10.0f); // Same value + } + + EXPECT_FLOAT_EQ (10.0f, text.getParagraphSpacing()); +} + +TEST (StyledTextTests, AlternatePropertyValues) +{ + StyledText text; + + for (int i = 0; i < 5; ++i) + { + { + auto modifier = text.startUpdate(); + modifier.setWrap (StyledText::wrap); + } + + EXPECT_EQ (StyledText::wrap, text.getWrap()); + + { + auto modifier = text.startUpdate(); + modifier.setWrap (StyledText::noWrap); + } + + EXPECT_EQ (StyledText::noWrap, text.getWrap()); + } +} + +// ============================================================================== +// Size Boundary Tests +// ============================================================================== + +TEST (StyledTextTests, SetMaxSizeWithVerySmallValues) +{ + StyledText text; + + { + auto modifier = text.startUpdate(); + modifier.setMaxSize (Size (0.001f, 0.001f)); + } + + Size maxSize = text.getMaxSize(); + EXPECT_FLOAT_EQ (0.001f, maxSize.getWidth()); + EXPECT_FLOAT_EQ (0.001f, maxSize.getHeight()); +} + +TEST (StyledTextTests, SetMaxSizeWithNegativeValues) +{ + StyledText text; + + { + auto modifier = text.startUpdate(); + modifier.setMaxSize (Size (-50.0f, -100.0f)); + } + + Size maxSize = text.getMaxSize(); + EXPECT_FLOAT_EQ (-50.0f, maxSize.getWidth()); + EXPECT_FLOAT_EQ (-100.0f, maxSize.getHeight()); +} + +TEST (StyledTextTests, SetParagraphSpacingWithVeryLargeValue) +{ + StyledText text; + + { + auto modifier = text.startUpdate(); + modifier.setParagraphSpacing (10000.0f); + } + + EXPECT_FLOAT_EQ (10000.0f, text.getParagraphSpacing()); +} + +TEST (StyledTextTests, SetParagraphSpacingWithVerySmallValue) +{ + StyledText text; + + { + auto modifier = text.startUpdate(); + modifier.setParagraphSpacing (0.0001f); + } + + EXPECT_FLOAT_EQ (0.0001f, text.getParagraphSpacing()); +} + +// ============================================================================== +// Alignment Combination Tests +// ============================================================================== + +TEST (StyledTextTests, AllHorizontalAlignmentOptions) +{ + StyledText text; + + { + auto modifier = text.startUpdate(); + modifier.setHorizontalAlign (StyledText::left); + } + EXPECT_EQ (StyledText::left, text.getHorizontalAlign()); + + { + auto modifier = text.startUpdate(); + modifier.setHorizontalAlign (StyledText::center); + } + EXPECT_EQ (StyledText::center, text.getHorizontalAlign()); + + { + auto modifier = text.startUpdate(); + modifier.setHorizontalAlign (StyledText::right); + } + EXPECT_EQ (StyledText::right, text.getHorizontalAlign()); + + { + auto modifier = text.startUpdate(); + modifier.setHorizontalAlign (StyledText::justified); + } + EXPECT_EQ (StyledText::justified, text.getHorizontalAlign()); +} + +TEST (StyledTextTests, AllVerticalAlignmentOptions) +{ + StyledText text; + + { + auto modifier = text.startUpdate(); + modifier.setVerticalAlign (StyledText::top); + } + EXPECT_EQ (StyledText::top, text.getVerticalAlign()); + + { + auto modifier = text.startUpdate(); + modifier.setVerticalAlign (StyledText::middle); + } + EXPECT_EQ (StyledText::middle, text.getVerticalAlign()); + + { + auto modifier = text.startUpdate(); + modifier.setVerticalAlign (StyledText::bottom); + } + EXPECT_EQ (StyledText::bottom, text.getVerticalAlign()); +} + +TEST (StyledTextTests, AllAlignmentCombinations) +{ + StyledText text; + + StyledText::HorizontalAlign hAligns[] = { + StyledText::left, StyledText::center, StyledText::right, StyledText::justified + }; + + StyledText::VerticalAlign vAligns[] = { + StyledText::top, StyledText::middle, StyledText::bottom + }; + + for (auto hAlign : hAligns) + { + for (auto vAlign : vAligns) + { + { + auto modifier = text.startUpdate(); + modifier.setHorizontalAlign (hAlign); + modifier.setVerticalAlign (vAlign); + } + + EXPECT_EQ (hAlign, text.getHorizontalAlign()); + EXPECT_EQ (vAlign, text.getVerticalAlign()); + } + } +} From dc85beab3ef6a8d952c100a415bcbddded8c506d Mon Sep 17 00:00:00 2001 From: kunitoki Date: Sun, 7 Dec 2025 10:56:25 +0100 Subject: [PATCH 04/30] More tweaks --- tests/yup_graphics/yup_Drawable.cpp | 818 ++++++++++++++++++++++++++ tests/yup_graphics/yup_Path.cpp | 432 ++++++++++++++ tests/yup_python/yup_ScriptEngine.cpp | 37 +- 3 files changed, 1277 insertions(+), 10 deletions(-) create mode 100644 tests/yup_graphics/yup_Drawable.cpp diff --git a/tests/yup_graphics/yup_Drawable.cpp b/tests/yup_graphics/yup_Drawable.cpp new file mode 100644 index 000000000..104010815 --- /dev/null +++ b/tests/yup_graphics/yup_Drawable.cpp @@ -0,0 +1,818 @@ +/* + ============================================================================== + + This file is part of the YUP library. + Copyright (c) 2025 - kunitoki@gmail.com + + YUP is an open source library subject to open-source licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + to use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + YUP IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +#include + +#include + +using namespace yup; + +// ============================================================================== +// Constructor and Default State Tests +// ============================================================================== + +TEST (DrawableTests, DefaultConstructorCreatesEmptyDrawable) +{ + Drawable drawable; + + Rectangle bounds = drawable.getBounds(); + EXPECT_EQ (0.0f, bounds.getWidth()); + EXPECT_EQ (0.0f, bounds.getHeight()); +} + +TEST (DrawableTests, DefaultBoundsAreEmpty) +{ + Drawable drawable; + + Rectangle bounds = drawable.getBounds(); + EXPECT_TRUE (bounds.isEmpty()); +} + +// ============================================================================== +// Clear Tests +// ============================================================================== + +TEST (DrawableTests, ClearResetsDrawable) +{ + Drawable drawable; + + drawable.clear(); + + Rectangle bounds = drawable.getBounds(); + EXPECT_TRUE (bounds.isEmpty()); +} + +TEST (DrawableTests, ClearMultipleTimes) +{ + Drawable drawable; + + drawable.clear(); + drawable.clear(); + drawable.clear(); + + Rectangle bounds = drawable.getBounds(); + EXPECT_TRUE (bounds.isEmpty()); +} + +// ============================================================================== +// Parse SVG Tests +// ============================================================================== + +TEST (DrawableTests, ParseNonExistentFileReturnsFalse) +{ + Drawable drawable; + File nonExistentFile ("/path/to/nonexistent/file.svg"); + + bool result = drawable.parseSVG (nonExistentFile); + + EXPECT_FALSE (result); +} + +TEST (DrawableTests, ParseDirectoryReturnsFalse) +{ + Drawable drawable; + File directory = File::getCurrentWorkingDirectory(); + + bool result = drawable.parseSVG (directory); + + EXPECT_FALSE (result); +} + +TEST (DrawableTests, ParseEmptyFileReturnsFalse) +{ + Drawable drawable; + + // Create a temporary empty file + File tempFile = File::getSpecialLocation (File::tempDirectory).getChildFile ("test_empty.svg"); + tempFile.replaceWithText (""); + + bool result = drawable.parseSVG (tempFile); + + EXPECT_FALSE (result); + + tempFile.deleteFile(); +} + +TEST (DrawableTests, ParseInvalidXMLReturnsFalse) +{ + Drawable drawable; + + // Create a temporary file with invalid XML + File tempFile = File::getSpecialLocation (File::tempDirectory).getChildFile ("test_invalid.svg"); + tempFile.replaceWithText ("This is not valid XML"); + + bool result = drawable.parseSVG (tempFile); + + EXPECT_FALSE (result); + + tempFile.deleteFile(); +} + +TEST (DrawableTests, ParseNonSVGXMLReturnsFalse) +{ + Drawable drawable; + + // Create a temporary file with valid XML but not SVG + File tempFile = File::getSpecialLocation (File::tempDirectory).getChildFile ("test_non_svg.xml"); + tempFile.replaceWithText ("data"); + + bool result = drawable.parseSVG (tempFile); + + EXPECT_FALSE (result); + + tempFile.deleteFile(); +} + +TEST (DrawableTests, ParseMinimalValidSVG) +{ + Drawable drawable; + + // Create a minimal valid SVG + File tempFile = File::getSpecialLocation (File::tempDirectory).getChildFile ("test_minimal.svg"); + tempFile.replaceWithText (""); + + bool result = drawable.parseSVG (tempFile); + + EXPECT_TRUE (result); + + tempFile.deleteFile(); +} + +TEST (DrawableTests, ParseSVGWithViewBox) +{ + Drawable drawable; + + // Create SVG with viewBox + File tempFile = File::getSpecialLocation (File::tempDirectory).getChildFile ("test_viewbox.svg"); + tempFile.replaceWithText (""); + + bool result = drawable.parseSVG (tempFile); + + EXPECT_TRUE (result); + + Rectangle bounds = drawable.getBounds(); + EXPECT_EQ (100.0f, bounds.getWidth()); + EXPECT_EQ (100.0f, bounds.getHeight()); + + tempFile.deleteFile(); +} + +TEST (DrawableTests, ParseSVGWithWidthHeight) +{ + Drawable drawable; + + // Create SVG with width and height + File tempFile = File::getSpecialLocation (File::tempDirectory).getChildFile ("test_size.svg"); + tempFile.replaceWithText (""); + + bool result = drawable.parseSVG (tempFile); + + EXPECT_TRUE (result); + + Rectangle bounds = drawable.getBounds(); + EXPECT_EQ (200.0f, bounds.getWidth()); + EXPECT_EQ (150.0f, bounds.getHeight()); + + tempFile.deleteFile(); +} + +TEST (DrawableTests, ParseSVGWithPathElement) +{ + Drawable drawable; + + File tempFile = File::getSpecialLocation (File::tempDirectory).getChildFile ("test_path.svg"); + tempFile.replaceWithText (""); + + bool result = drawable.parseSVG (tempFile); + + EXPECT_TRUE (result); + + tempFile.deleteFile(); +} + +TEST (DrawableTests, ClearAfterParseResetsDrawable) +{ + Drawable drawable; + + File tempFile = File::getSpecialLocation (File::tempDirectory).getChildFile ("test_clear_after.svg"); + tempFile.replaceWithText (""); + + drawable.parseSVG (tempFile); + drawable.clear(); + + Rectangle bounds = drawable.getBounds(); + EXPECT_TRUE (bounds.isEmpty()); + + tempFile.deleteFile(); +} + +// ============================================================================== +// Paint Tests (Basic) +// ============================================================================== + +TEST (DrawableTests, PaintEmptyDrawableDoesNotCrash) +{ + Drawable drawable; + + auto context = GraphicsContext::createContext (GraphicsContext::Headless, {}); + auto renderer = context->makeRenderer (100, 100); + Graphics graphics (*context, *renderer); + + // Should not crash + drawable.paint (graphics); +} + +TEST (DrawableTests, PaintWithFittingDoesNotCrash) +{ + Drawable drawable; + + auto context = GraphicsContext::createContext (GraphicsContext::Headless, {}); + auto renderer = context->makeRenderer (100, 100); + Graphics graphics (*context, *renderer); + + Rectangle targetArea (0.0f, 0.0f, 100.0f, 100.0f); + + // Should not crash with empty drawable + drawable.paint (graphics, targetArea, Fitting::scaleToFit, Justification::center); +} + +TEST (DrawableTests, PaintWithVariousFittingModes) +{ + Drawable drawable; + + auto context = GraphicsContext::createContext (GraphicsContext::Headless, {}); + auto renderer = context->makeRenderer (100, 100); + Graphics graphics (*context, *renderer); + + Rectangle targetArea (0.0f, 0.0f, 100.0f, 100.0f); + + Fitting fittingModes[] = { + Fitting::none, + Fitting::scaleToFit, + Fitting::fitWidth, + Fitting::fitHeight, + Fitting::scaleToFill, + Fitting::fill, + Fitting::centerInside, + Fitting::centerCrop, + Fitting::stretchWidth, + Fitting::stretchHeight, + Fitting::tile + }; + + for (auto fitting : fittingModes) + { + // Should not crash + drawable.paint (graphics, targetArea, fitting, Justification::center); + } +} + +TEST (DrawableTests, PaintWithVariousJustifications) +{ + Drawable drawable; + + auto context = GraphicsContext::createContext (GraphicsContext::Headless, {}); + auto renderer = context->makeRenderer (100, 100); + Graphics graphics (*context, *renderer); + + Rectangle targetArea (0.0f, 0.0f, 100.0f, 100.0f); + + Justification justifications[] = { + Justification::topLeft, + Justification::centerTop, + Justification::topRight, + Justification::centerLeft, + Justification::center, + Justification::centerRight, + Justification::bottomLeft, + Justification::centerBottom, + Justification::bottomRight + }; + + for (auto justification : justifications) + { + // Should not crash + drawable.paint (graphics, targetArea, Fitting::scaleToFit, justification); + } +} + +TEST (DrawableTests, PaintWithEmptyTargetArea) +{ + Drawable drawable; + + auto context = GraphicsContext::createContext (GraphicsContext::Headless, {}); + auto renderer = context->makeRenderer (100, 100); + Graphics graphics (*context, *renderer); + + Rectangle emptyArea (0.0f, 0.0f, 0.0f, 0.0f); + + // Should not crash or render anything + drawable.paint (graphics, emptyArea, Fitting::scaleToFit, Justification::center); +} + +TEST (DrawableTests, PaintWithNegativeArea) +{ + Drawable drawable; + + auto context = GraphicsContext::createContext (GraphicsContext::Headless, {}); + auto renderer = context->makeRenderer (100, 100); + Graphics graphics (*context, *renderer); + + Rectangle negativeArea (0.0f, 0.0f, -100.0f, -100.0f); + + // Should handle gracefully + drawable.paint (graphics, negativeArea, Fitting::scaleToFit, Justification::center); +} + +// ============================================================================== +// Multiple Parse Tests +// ============================================================================== + +TEST (DrawableTests, ParseMultipleTimes) +{ + Drawable drawable; + + File tempFile = File::getSpecialLocation (File::tempDirectory).getChildFile ("test_multiple.svg"); + tempFile.replaceWithText (""); + + bool result1 = drawable.parseSVG (tempFile); + bool result2 = drawable.parseSVG (tempFile); + + EXPECT_TRUE (result1); + EXPECT_TRUE (result2); + + tempFile.deleteFile(); +} + +TEST (DrawableTests, ParseDifferentFilesClearsOldContent) +{ + Drawable drawable; + + File tempFile1 = File::getSpecialLocation (File::tempDirectory).getChildFile ("test1.svg"); + tempFile1.replaceWithText (""); + + File tempFile2 = File::getSpecialLocation (File::tempDirectory).getChildFile ("test2.svg"); + tempFile2.replaceWithText (""); + + drawable.parseSVG (tempFile1); + drawable.parseSVG (tempFile2); + + Rectangle bounds = drawable.getBounds(); + EXPECT_EQ (200.0f, bounds.getWidth()); + EXPECT_EQ (200.0f, bounds.getHeight()); + + tempFile1.deleteFile(); + tempFile2.deleteFile(); +} + +// ============================================================================== +// SVG Element Tests +// ============================================================================== + +TEST (DrawableTests, ParseSVGWithRectElement) +{ + Drawable drawable; + + File tempFile = File::getSpecialLocation (File::tempDirectory).getChildFile ("test_rect.svg"); + tempFile.replaceWithText (""); + + bool result = drawable.parseSVG (tempFile); + + EXPECT_TRUE (result); + + tempFile.deleteFile(); +} + +TEST (DrawableTests, ParseSVGWithCircleElement) +{ + Drawable drawable; + + File tempFile = File::getSpecialLocation (File::tempDirectory).getChildFile ("test_circle.svg"); + tempFile.replaceWithText (""); + + bool result = drawable.parseSVG (tempFile); + + EXPECT_TRUE (result); + + tempFile.deleteFile(); +} + +TEST (DrawableTests, ParseSVGWithEllipseElement) +{ + Drawable drawable; + + File tempFile = File::getSpecialLocation (File::tempDirectory).getChildFile ("test_ellipse.svg"); + tempFile.replaceWithText (""); + + bool result = drawable.parseSVG (tempFile); + + EXPECT_TRUE (result); + + tempFile.deleteFile(); +} + +TEST (DrawableTests, ParseSVGWithLineElement) +{ + Drawable drawable; + + File tempFile = File::getSpecialLocation (File::tempDirectory).getChildFile ("test_line.svg"); + tempFile.replaceWithText (""); + + bool result = drawable.parseSVG (tempFile); + + EXPECT_TRUE (result); + + tempFile.deleteFile(); +} + +TEST (DrawableTests, ParseSVGWithPolygonElement) +{ + Drawable drawable; + + File tempFile = File::getSpecialLocation (File::tempDirectory).getChildFile ("test_polygon.svg"); + tempFile.replaceWithText (""); + + bool result = drawable.parseSVG (tempFile); + + EXPECT_TRUE (result); + + tempFile.deleteFile(); +} + +TEST (DrawableTests, ParseSVGWithPolylineElement) +{ + Drawable drawable; + + File tempFile = File::getSpecialLocation (File::tempDirectory).getChildFile ("test_polyline.svg"); + tempFile.replaceWithText (""); + + bool result = drawable.parseSVG (tempFile); + + EXPECT_TRUE (result); + + tempFile.deleteFile(); +} + +TEST (DrawableTests, ParseSVGWithGroupElement) +{ + Drawable drawable; + + File tempFile = File::getSpecialLocation (File::tempDirectory).getChildFile ("test_group.svg"); + tempFile.replaceWithText (""); + + bool result = drawable.parseSVG (tempFile); + + EXPECT_TRUE (result); + + tempFile.deleteFile(); +} + +TEST (DrawableTests, ParseSVGWithNestedGroups) +{ + Drawable drawable; + + File tempFile = File::getSpecialLocation (File::tempDirectory).getChildFile ("test_nested.svg"); + tempFile.replaceWithText (""); + + bool result = drawable.parseSVG (tempFile); + + EXPECT_TRUE (result); + + tempFile.deleteFile(); +} + +// ============================================================================== +// SVG Style Tests +// ============================================================================== + +TEST (DrawableTests, ParseSVGWithFillColor) +{ + Drawable drawable; + + File tempFile = File::getSpecialLocation (File::tempDirectory).getChildFile ("test_fill.svg"); + tempFile.replaceWithText (""); + + bool result = drawable.parseSVG (tempFile); + + EXPECT_TRUE (result); + + tempFile.deleteFile(); +} + +TEST (DrawableTests, ParseSVGWithStrokeColor) +{ + Drawable drawable; + + File tempFile = File::getSpecialLocation (File::tempDirectory).getChildFile ("test_stroke.svg"); + tempFile.replaceWithText (""); + + bool result = drawable.parseSVG (tempFile); + + EXPECT_TRUE (result); + + tempFile.deleteFile(); +} + +TEST (DrawableTests, ParseSVGWithOpacity) +{ + Drawable drawable; + + File tempFile = File::getSpecialLocation (File::tempDirectory).getChildFile ("test_opacity.svg"); + tempFile.replaceWithText (""); + + bool result = drawable.parseSVG (tempFile); + + EXPECT_TRUE (result); + + tempFile.deleteFile(); +} + +TEST (DrawableTests, ParseSVGWithTransform) +{ + Drawable drawable; + + File tempFile = File::getSpecialLocation (File::tempDirectory).getChildFile ("test_transform.svg"); + tempFile.replaceWithText (""); + + bool result = drawable.parseSVG (tempFile); + + EXPECT_TRUE (result); + + tempFile.deleteFile(); +} + +TEST (DrawableTests, ParseSVGWithStyleAttribute) +{ + Drawable drawable; + + File tempFile = File::getSpecialLocation (File::tempDirectory).getChildFile ("test_style.svg"); + tempFile.replaceWithText (""); + + bool result = drawable.parseSVG (tempFile); + + EXPECT_TRUE (result); + + tempFile.deleteFile(); +} + +// ============================================================================== +// SVG Transform Tests +// ============================================================================== + +TEST (DrawableTests, ParseTransformTranslate) +{ + Drawable drawable; + + File tempFile = File::getSpecialLocation (File::tempDirectory).getChildFile ("test_translate.svg"); + tempFile.replaceWithText (""); + + bool result = drawable.parseSVG (tempFile); + + EXPECT_TRUE (result); + + tempFile.deleteFile(); +} + +TEST (DrawableTests, ParseTransformScale) +{ + Drawable drawable; + + File tempFile = File::getSpecialLocation (File::tempDirectory).getChildFile ("test_scale.svg"); + tempFile.replaceWithText (""); + + bool result = drawable.parseSVG (tempFile); + + EXPECT_TRUE (result); + + tempFile.deleteFile(); +} + +TEST (DrawableTests, ParseTransformRotate) +{ + Drawable drawable; + + File tempFile = File::getSpecialLocation (File::tempDirectory).getChildFile ("test_rotate.svg"); + tempFile.replaceWithText (""); + + bool result = drawable.parseSVG (tempFile); + + EXPECT_TRUE (result); + + tempFile.deleteFile(); +} + +TEST (DrawableTests, ParseTransformMatrix) +{ + Drawable drawable; + + File tempFile = File::getSpecialLocation (File::tempDirectory).getChildFile ("test_matrix.svg"); + tempFile.replaceWithText (""); + + bool result = drawable.parseSVG (tempFile); + + EXPECT_TRUE (result); + + tempFile.deleteFile(); +} + +TEST (DrawableTests, ParseMultipleTransforms) +{ + Drawable drawable; + + File tempFile = File::getSpecialLocation (File::tempDirectory).getChildFile ("test_multi_transform.svg"); + tempFile.replaceWithText (""); + + bool result = drawable.parseSVG (tempFile); + + EXPECT_TRUE (result); + + tempFile.deleteFile(); +} + +// ============================================================================== +// SVG Gradient Tests +// ============================================================================== + +TEST (DrawableTests, ParseSVGWithLinearGradient) +{ + Drawable drawable; + + File tempFile = File::getSpecialLocation (File::tempDirectory).getChildFile ("test_linear_gradient.svg"); + tempFile.replaceWithText ( + "" + "" + "" + "" + ""); + + bool result = drawable.parseSVG (tempFile); + + EXPECT_TRUE (result); + + tempFile.deleteFile(); +} + +TEST (DrawableTests, ParseSVGWithRadialGradient) +{ + Drawable drawable; + + File tempFile = File::getSpecialLocation (File::tempDirectory).getChildFile ("test_radial_gradient.svg"); + tempFile.replaceWithText ( + "" + "" + "" + "" + ""); + + bool result = drawable.parseSVG (tempFile); + + EXPECT_TRUE (result); + + tempFile.deleteFile(); +} + +// ============================================================================== +// Edge Cases and Error Handling +// ============================================================================== + +TEST (DrawableTests, ParseSVGWithInvalidPath) +{ + Drawable drawable; + + File tempFile = File::getSpecialLocation (File::tempDirectory).getChildFile ("test_invalid_path.svg"); + tempFile.replaceWithText (""); + + bool result = drawable.parseSVG (tempFile); + + // Path::fromString always returns true, so parsing succeeds even with invalid data + EXPECT_TRUE (result); + + tempFile.deleteFile(); +} + +TEST (DrawableTests, ParseSVGWithEmptyPath) +{ + Drawable drawable; + + File tempFile = File::getSpecialLocation (File::tempDirectory).getChildFile ("test_empty_path.svg"); + tempFile.replaceWithText (""); + + bool result = drawable.parseSVG (tempFile); + + // Path::fromString always returns true, so parsing succeeds even with empty path + EXPECT_TRUE (result); + + tempFile.deleteFile(); +} + +TEST (DrawableTests, ParseSVGWithMalformedViewBox) +{ + Drawable drawable; + + File tempFile = File::getSpecialLocation (File::tempDirectory).getChildFile ("test_malformed_viewbox.svg"); + tempFile.replaceWithText (""); + + bool result = drawable.parseSVG (tempFile); + + // Should still parse the SVG element + EXPECT_TRUE (result); + + tempFile.deleteFile(); +} + +TEST (DrawableTests, ParseSVGWithPartialViewBox) +{ + Drawable drawable; + + File tempFile = File::getSpecialLocation (File::tempDirectory).getChildFile ("test_partial_viewbox.svg"); + tempFile.replaceWithText (""); // Only 2 values instead of 4 + + bool result = drawable.parseSVG (tempFile); + + EXPECT_TRUE (result); + + tempFile.deleteFile(); +} + +TEST (DrawableTests, ParseSVGWithNegativeDimensions) +{ + Drawable drawable; + + File tempFile = File::getSpecialLocation (File::tempDirectory).getChildFile ("test_negative_dims.svg"); + tempFile.replaceWithText (""); + + bool result = drawable.parseSVG (tempFile); + + EXPECT_TRUE (result); + + tempFile.deleteFile(); +} + +TEST (DrawableTests, ParseSVGWithZeroDimensions) +{ + Drawable drawable; + + File tempFile = File::getSpecialLocation (File::tempDirectory).getChildFile ("test_zero_dims.svg"); + tempFile.replaceWithText (""); + + bool result = drawable.parseSVG (tempFile); + + EXPECT_TRUE (result); + + tempFile.deleteFile(); +} + +// ============================================================================== +// Bounds Calculation Tests +// ============================================================================== + +TEST (DrawableTests, GetBoundsAfterClear) +{ + Drawable drawable; + + File tempFile = File::getSpecialLocation (File::tempDirectory).getChildFile ("test_bounds_clear.svg"); + tempFile.replaceWithText (""); + + drawable.parseSVG (tempFile); + drawable.clear(); + + Rectangle bounds = drawable.getBounds(); + EXPECT_TRUE (bounds.isEmpty()); + + tempFile.deleteFile(); +} + +TEST (DrawableTests, GetBoundsWithViewBoxTakesPrecedence) +{ + Drawable drawable; + + File tempFile = File::getSpecialLocation (File::tempDirectory).getChildFile ("test_bounds_viewbox.svg"); + tempFile.replaceWithText (""); + + drawable.parseSVG (tempFile); + + Rectangle bounds = drawable.getBounds(); + // ViewBox should take precedence + EXPECT_EQ (100.0f, bounds.getWidth()); + EXPECT_EQ (100.0f, bounds.getHeight()); + + tempFile.deleteFile(); +} diff --git a/tests/yup_graphics/yup_Path.cpp b/tests/yup_graphics/yup_Path.cpp index 62df26623..4b4163d01 100644 --- a/tests/yup_graphics/yup_Path.cpp +++ b/tests/yup_graphics/yup_Path.cpp @@ -550,3 +550,435 @@ TEST (PathTests, ScaleToFitPractical) EXPECT_NEAR (b.getWidth(), 100.0f, tol); EXPECT_NEAR (b.getHeight(), 50.0f, tol); } + +// ============================================================================== +// Tests for uncovered methods +// ============================================================================== + +TEST (PathTests, ConstructorWithPoint) +{ + Point p (10.0f, 20.0f); + Path path (p); + EXPECT_GT (path.size(), 0); +} + +TEST (PathTests, QuadToWithPointParameter) +{ + Path p; + p.moveTo (0, 0); + Point controlPoint (5, 5); + p.quadTo (controlPoint, 10, 0); + EXPECT_FALSE (p.getBounds().isEmpty()); +} + +TEST (PathTests, CubicToWithPointParameter) +{ + Path p; + p.moveTo (0, 0); + Point controlPoint1 (3, 5); + p.cubicTo (controlPoint1, 7, 5, 10, 0); + EXPECT_FALSE (p.getBounds().isEmpty()); +} + +TEST (PathTests, CreateCopy) +{ + Path p1; + p1.addRectangle (0, 0, 10, 10); + p1.addEllipse (5, 5, 15, 15); + + Path p2 = p1.createCopy(); + + EXPECT_EQ (p1.size(), p2.size()); + expectRectNear (p1.getBounds(), p2.getBounds()); +} + +TEST (PathTests, CreateCopyEmpty) +{ + Path p1; + Path p2 = p1.createCopy(); + + EXPECT_EQ (p1.size(), p2.size()); + EXPECT_TRUE (p2.getBounds().isEmpty()); +} + +TEST (PathTests, IteratorPostfixIncrement) +{ + Path p; + p.addRectangle (0, 0, 10, 10); + + auto it = p.begin(); + auto end = p.end(); + int count = 0; + + while (it != end) + { + it++; // Postfix increment + ++count; + } + + EXPECT_GT (count, 0); +} + +TEST (PathTests, FromStringQuadraticBezierAbsolute) +{ + Path p; + bool ok = p.fromString ("M 10 80 Q 52.5 10, 95 80"); + EXPECT_TRUE (ok); + EXPECT_FALSE (p.getBounds().isEmpty()); +} + +TEST (PathTests, FromStringQuadraticBezierRelative) +{ + Path p; + bool ok = p.fromString ("M 10 80 q 42.5 -70, 85 0"); + EXPECT_TRUE (ok); + EXPECT_FALSE (p.getBounds().isEmpty()); +} + +TEST (PathTests, FromStringSmoothQuadraticAbsolute) +{ + Path p; + bool ok = p.fromString ("M 10 80 Q 52.5 10, 95 80 T 180 80"); + EXPECT_TRUE (ok); + EXPECT_FALSE (p.getBounds().isEmpty()); +} + +TEST (PathTests, FromStringSmoothQuadraticRelative) +{ + Path p; + bool ok = p.fromString ("M 10 80 Q 52.5 10, 95 80 t 85 0"); + EXPECT_TRUE (ok); + EXPECT_FALSE (p.getBounds().isEmpty()); +} + +TEST (PathTests, FromStringCubicBezierAbsolute) +{ + Path p; + bool ok = p.fromString ("M 10 10 C 20 20, 40 20, 50 10"); + EXPECT_TRUE (ok); + EXPECT_FALSE (p.getBounds().isEmpty()); +} + +TEST (PathTests, FromStringCubicBezierRelative) +{ + Path p; + bool ok = p.fromString ("M 10 10 c 10 10, 30 10, 40 0"); + EXPECT_TRUE (ok); + EXPECT_FALSE (p.getBounds().isEmpty()); +} + +TEST (PathTests, FromStringSmoothCubicAbsolute) +{ + Path p; + bool ok = p.fromString ("M 10 80 C 40 10, 65 10, 95 80 S 150 150, 180 80"); + EXPECT_TRUE (ok); + EXPECT_FALSE (p.getBounds().isEmpty()); +} + +TEST (PathTests, FromStringSmoothCubicRelative) +{ + Path p; + bool ok = p.fromString ("M 10 80 C 40 10, 65 10, 95 80 s 55 70, 85 0"); + EXPECT_TRUE (ok); + EXPECT_FALSE (p.getBounds().isEmpty()); +} + +TEST (PathTests, FromStringEllipticalArcAbsolute) +{ + Path p; + bool ok = p.fromString ("M 10 20 A 20 20 0 0 1 50 20"); + EXPECT_TRUE (ok); + EXPECT_FALSE (p.getBounds().isEmpty()); +} + +TEST (PathTests, FromStringEllipticalArcRelative) +{ + Path p; + bool ok = p.fromString ("M 10 20 a 20 20 0 0 1 40 0"); + EXPECT_TRUE (ok); + EXPECT_FALSE (p.getBounds().isEmpty()); +} + +TEST (PathTests, FromStringEllipticalArcLargeArc) +{ + Path p; + bool ok = p.fromString ("M 10 20 A 30 30 0 1 0 50 20"); + EXPECT_TRUE (ok); + EXPECT_FALSE (p.getBounds().isEmpty()); +} + +TEST (PathTests, FromStringEllipticalArcSweep) +{ + Path p; + bool ok = p.fromString ("M 10 20 A 30 30 45 0 1 50 20"); + EXPECT_TRUE (ok); + EXPECT_FALSE (p.getBounds().isEmpty()); +} + +TEST (PathTests, FromStringEllipticalArcDegenerateToLine) +{ + Path p; + bool ok = p.fromString ("M 10 20 A 0 0 0 0 1 50 20"); + EXPECT_TRUE (ok); + EXPECT_FALSE (p.getBounds().isEmpty()); +} + +TEST (PathTests, FromStringComplexPath) +{ + Path p; + bool ok = p.fromString ("M 10 10 L 20 20 Q 30 30, 40 20 C 50 10, 60 10, 70 20 S 90 40, 100 20 T 120 20 A 10 10 0 0 1 140 20 Z"); + EXPECT_TRUE (ok); + EXPECT_FALSE (p.getBounds().isEmpty()); +} + +TEST (PathTests, GetPointAlongPathQuadratic) +{ + Path p; + p.moveTo (0, 0).quadTo (10, 10, 5, 5).close(); + + Point start = p.getPointAlongPath (0.0f); + Point mid = p.getPointAlongPath (0.5f); + Point end = p.getPointAlongPath (1.0f); + + expectPointNear (start, Point (0, 0)); + EXPECT_TRUE (mid.getX() >= 0 && mid.getX() <= 10); + EXPECT_TRUE (mid.getY() >= 0 && mid.getY() <= 10); +} + +TEST (PathTests, GetPointAlongPathCubic) +{ + Path p; + p.moveTo (0, 0).cubicTo (10, 0, 5, 5, 15, 5).close(); + + Point start = p.getPointAlongPath (0.0f); + Point mid = p.getPointAlongPath (0.5f); + Point end = p.getPointAlongPath (1.0f); + + expectPointNear (start, Point (0, 0)); + EXPECT_TRUE (mid.getX() >= 0 && mid.getX() <= 15); + EXPECT_TRUE (mid.getY() >= 0 && mid.getY() <= 5); +} + +TEST (PathTests, GetPointAlongPathMixedSegments) +{ + Path p; + p.moveTo (0, 0) + .lineTo (10, 0) + .quadTo (15, 5, 10, 10) + .cubicTo (5, 15, 0, 10, 0, 0) + .close(); + + Point p1 = p.getPointAlongPath (0.0f); + Point p2 = p.getPointAlongPath (0.25f); + Point p3 = p.getPointAlongPath (0.5f); + Point p4 = p.getPointAlongPath (0.75f); + Point p5 = p.getPointAlongPath (1.0f); + + expectPointNear (p1, Point (0, 0)); + EXPECT_TRUE (p2.getX() >= 0 && p2.getX() <= 15); + EXPECT_TRUE (p3.getX() >= 0 && p3.getX() <= 15); + EXPECT_TRUE (p4.getX() >= 0 && p4.getX() <= 15); +} + +TEST (PathTests, CreateStrokePolygonLine) +{ + Path p; + p.moveTo (0, 0).lineTo (10, 0); + + Path stroke = p.createStrokePolygon (2.0f); + EXPECT_FALSE (stroke.getBounds().isEmpty()); + EXPECT_GT (stroke.size(), 0); +} + +TEST (PathTests, CreateStrokePolygonQuadratic) +{ + Path p; + p.moveTo (0, 0).quadTo (10, 10, 5, 5); + + Path stroke = p.createStrokePolygon (2.0f); + EXPECT_FALSE (stroke.getBounds().isEmpty()); + EXPECT_GT (stroke.size(), 0); +} + +TEST (PathTests, CreateStrokePolygonCubic) +{ + Path p; + p.moveTo (0, 0).cubicTo (10, 0, 5, 5, 15, 5); + + Path stroke = p.createStrokePolygon (2.0f); + EXPECT_FALSE (stroke.getBounds().isEmpty()); + EXPECT_GT (stroke.size(), 0); +} + +TEST (PathTests, CreateStrokePolygonClosedPath) +{ + Path p; + p.moveTo (0, 0).lineTo (10, 0).lineTo (10, 10).lineTo (0, 10).close(); + + Path stroke = p.createStrokePolygon (2.0f); + EXPECT_FALSE (stroke.getBounds().isEmpty()); + EXPECT_GT (stroke.size(), 0); +} + +TEST (PathTests, CreateStrokePolygonMixedCommands) +{ + Path p; + p.moveTo (0, 0) + .lineTo (10, 0) + .quadTo (15, 5, 10, 10) + .cubicTo (5, 15, 0, 10, 0, 0) + .close(); + + Path stroke = p.createStrokePolygon (2.0f); + EXPECT_FALSE (stroke.getBounds().isEmpty()); + EXPECT_GT (stroke.size(), 0); +} + +TEST (PathTests, AddBubbleArrowTop) +{ + Path p; + Rectangle body (50, 50, 100, 50); + Rectangle max (0, 0, 200, 200); + Point tip (100, 10); + + p.addBubble (body, max, tip, 5, 10); + EXPECT_FALSE (p.getBounds().isEmpty()); +} + +TEST (PathTests, AddBubbleArrowBottom) +{ + Path p; + Rectangle body (50, 50, 100, 50); + Rectangle max (0, 0, 200, 200); + Point tip (100, 180); + + p.addBubble (body, max, tip, 5, 10); + EXPECT_FALSE (p.getBounds().isEmpty()); +} + +TEST (PathTests, AddBubbleArrowLeft) +{ + Path p; + Rectangle body (50, 50, 100, 50); + Rectangle max (0, 0, 200, 200); + Point tip (10, 75); + + p.addBubble (body, max, tip, 5, 10); + EXPECT_FALSE (p.getBounds().isEmpty()); +} + +TEST (PathTests, AddBubbleArrowRight) +{ + Path p; + Rectangle body (50, 50, 100, 50); + Rectangle max (0, 0, 200, 200); + Point tip (180, 75); + + p.addBubble (body, max, tip, 5, 10); + EXPECT_FALSE (p.getBounds().isEmpty()); +} + +TEST (PathTests, AddBubbleArrowTopLeft) +{ + Path p; + Rectangle body (50, 50, 100, 50); + Rectangle max (0, 0, 200, 200); + Point tip (30, 30); + + p.addBubble (body, max, tip, 5, 10); + EXPECT_FALSE (p.getBounds().isEmpty()); +} + +TEST (PathTests, AddBubbleArrowTopRight) +{ + Path p; + Rectangle body (50, 50, 100, 50); + Rectangle max (0, 0, 200, 200); + Point tip (170, 30); + + p.addBubble (body, max, tip, 5, 10); + EXPECT_FALSE (p.getBounds().isEmpty()); +} + +TEST (PathTests, AddBubbleArrowBottomLeft) +{ + Path p; + Rectangle body (50, 50, 100, 50); + Rectangle max (0, 0, 200, 200); + Point tip (30, 170); + + p.addBubble (body, max, tip, 5, 10); + EXPECT_FALSE (p.getBounds().isEmpty()); +} + +TEST (PathTests, AddBubbleArrowBottomRight) +{ + Path p; + Rectangle body (50, 50, 100, 50); + Rectangle max (0, 0, 200, 200); + Point tip (170, 170); + + p.addBubble (body, max, tip, 5, 10); + EXPECT_FALSE (p.getBounds().isEmpty()); +} + +TEST (PathTests, AppendPathWithTransformTranslation) +{ + Path p1; + p1.addRectangle (0, 0, 10, 10); + + Path p2; + p2.addEllipse (0, 0, 5, 5); + + AffineTransform t = AffineTransform::translation (20, 20); + p1.appendPath (p2, t); + + Rectangle bounds = p1.getBounds(); + EXPECT_GE (bounds.getWidth(), 15.0f); +} + +TEST (PathTests, AppendPathWithTransformScaling) +{ + Path p1; + p1.addRectangle (0, 0, 10, 10); + + Path p2; + p2.addRectangle (0, 0, 5, 5); + + AffineTransform t = AffineTransform::scaling (2.0f); + p1.appendPath (p2, t); + + Rectangle bounds = p1.getBounds(); + EXPECT_GE (bounds.getWidth(), 10.0f); +} + +TEST (PathTests, AppendPathWithTransformRotation) +{ + Path p1; + p1.addRectangle (0, 0, 10, 10); + + Path p2; + p2.addRectangle (10, 0, 5, 5); + + AffineTransform t = AffineTransform::rotation (MathConstants::halfPi); + p1.appendPath (p2, t); + + EXPECT_FALSE (p1.getBounds().isEmpty()); +} + +TEST (PathTests, AppendPathWithTransformComplex) +{ + Path p1; + p1.addRectangle (0, 0, 10, 10); + + Path p2; + p2.addEllipse (0, 0, 8, 8); + + AffineTransform t = AffineTransform::translation (10, 10) + .scaled (1.5f) + .rotated (MathConstants::quarterPi); + p1.appendPath (p2, t); + + EXPECT_FALSE (p1.getBounds().isEmpty()); + EXPECT_GT (p1.size(), 0); +} diff --git a/tests/yup_python/yup_ScriptEngine.cpp b/tests/yup_python/yup_ScriptEngine.cpp index 49782441b..1eb0ebb4c 100644 --- a/tests/yup_python/yup_ScriptEngine.cpp +++ b/tests/yup_python/yup_ScriptEngine.cpp @@ -312,17 +312,34 @@ TEST_F (ScriptEngineTest, PrepareScriptingHomeWithValidParameters) TEST_F (ScriptEngineTest, PrepareScriptingHomeWithForceInstall) { - ScriptEngine engine; + auto tempDir = File::createTempFile ("test_home_force"); + tempDir.deleteFile(); - auto result = engine.runScript (String (R"( - try: - import non_existent_module - except ImportError as e: - print(f"Import error: {e}") - result = "import_failed" - )") - .dedentLines()); - EXPECT_TRUE (result.wasOk()); + auto standardLibraryCallback = [] (const char* name) -> MemoryBlock + { + return MemoryBlock(); + }; + + // First call without force install + auto config1 = ScriptEngine::prepareScriptingHome ( + tempDir, + standardLibraryCallback, + false); + + EXPECT_NE (nullptr, config1); + EXPECT_TRUE (tempDir.isDirectory()); + + // Second call with force install (should recreate) + auto config2 = ScriptEngine::prepareScriptingHome ( + tempDir, + standardLibraryCallback, + true); + + EXPECT_NE (nullptr, config2); + EXPECT_TRUE (tempDir.isDirectory()); + + // Clean up + tempDir.deleteRecursively(); } TEST_F (ScriptEngineTest, RunScriptWithExceptionHandling) From 026ab2dede5faea55565309d078961b39f139121 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Sun, 7 Dec 2025 14:53:25 +0100 Subject: [PATCH 05/30] Fix issues with tests --- modules/yup_core/files/yup_File.cpp | 6 + .../yup_python/scripting/yup_ScriptEngine.cpp | 4 +- tests/yup_core/yup_URL.cpp | 258 +++++++++--------- 3 files changed, 135 insertions(+), 133 deletions(-) diff --git a/modules/yup_core/files/yup_File.cpp b/modules/yup_core/files/yup_File.cpp index f4adc2179..b6a09e858 100644 --- a/modules/yup_core/files/yup_File.cpp +++ b/modules/yup_core/files/yup_File.cpp @@ -803,6 +803,9 @@ bool File::startAsProcess (const String& parameters, const StringPairArray& envi //============================================================================== std::unique_ptr File::createInputStream() const { + if (isDirectory()) + return nullptr; + auto fin = std::make_unique (*this); if (fin->openedOk()) @@ -813,6 +816,9 @@ std::unique_ptr File::createInputStream() const std::unique_ptr File::createOutputStream (size_t bufferSize) const { + if (isDirectory()) + return nullptr; + auto fout = std::make_unique (*this, bufferSize); if (fout->openedOk()) diff --git a/modules/yup_python/scripting/yup_ScriptEngine.cpp b/modules/yup_python/scripting/yup_ScriptEngine.cpp index 0442778e6..d86af9bdf 100644 --- a/modules/yup_python/scripting/yup_ScriptEngine.cpp +++ b/modules/yup_python/scripting/yup_ScriptEngine.cpp @@ -114,8 +114,8 @@ std::unique_ptr ScriptEngine::prepareScriptingHome ( zip.uncompressTo (libFolder.getParentDirectory()); } - for (auto entry : RangedDirectoryIterator (destinationFolder, true, "*", File::findFiles, File::FollowSymlinks::no)) - YUP_DBG (entry.getFile().getFullPathName()); + //for (auto entry : RangedDirectoryIterator (destinationFolder, true, "*", File::findFiles, File::FollowSymlinks::no)) + // YUP_DBG (entry.getFile().getFullPathName()); PyPreConfig preconfig; PyPreConfig_InitIsolatedConfig (&preconfig); diff --git a/tests/yup_core/yup_URL.cpp b/tests/yup_core/yup_URL.cpp index 8cb286d29..25f72331f 100644 --- a/tests/yup_core/yup_URL.cpp +++ b/tests/yup_core/yup_URL.cpp @@ -617,62 +617,6 @@ TEST_F (URLTests, StaticUtilityMethods) EXPECT_EQ (unparsedUrl.toString (false), urlWithParams); } -#if ! YUP_WASM -TEST_F (URLTests, LocalFileStreams) -{ - URL fileUrl (testFile); - - // Test input stream - if (auto inputStream = fileUrl.createInputStream (URL::InputStreamOptions (URL::ParameterHandling::inAddress))) - { - EXPECT_EQ (inputStream->getTotalLength(), testFile.getSize()); - - MemoryBlock readData; - inputStream->readIntoMemoryBlock (readData); - EXPECT_EQ (readData.toString(), "Test content"); - } - - // Test output stream - File tempFile = File::getSpecialLocation (File::tempDirectory).getChildFile ("test_output.txt"); - URL outputUrl (tempFile); - - if (auto outputStream = outputUrl.createOutputStream()) - { - outputStream->writeText ("Test output", false, false, nullptr); - outputStream.reset(); - - EXPECT_TRUE (tempFile.existsAsFile()); - EXPECT_EQ (tempFile.loadFileAsString(), "Test output"); - tempFile.deleteFile(); - } - - // Test readEntireBinaryStream - MemoryBlock binaryData; - EXPECT_TRUE (fileUrl.readEntireBinaryStream (binaryData, false)); - EXPECT_EQ (binaryData.toString(), "Test content"); - - // Test readEntireTextStream - String textData = fileUrl.readEntireTextStream (false); - EXPECT_EQ (textData, "Test content"); - - // Test readEntireXmlStream with XML content - File xmlFile = File::getSpecialLocation (File::tempDirectory).getChildFile ("test.xml"); - xmlFile.replaceWithText ("content"); - URL xmlUrl (xmlFile); - - if (auto xml = xmlUrl.readEntireXmlStream (false)) - { - EXPECT_EQ (xml->getTagName(), "root"); - if (auto child = xml->getChildByName ("child")) - { - EXPECT_EQ (child->getStringAttribute ("attr"), "value"); - EXPECT_EQ (child->getAllSubText(), "content"); - } - } - xmlFile.deleteFile(); -} -#endif - // Additional edge cases TEST_F (URLTests, EdgeCases) { @@ -700,45 +644,6 @@ TEST_F (URLTests, EdgeCases) EXPECT_EQ (fullUrl.getAnchorString(), "#section"); } -#if ! YUP_WASM -TEST_F (URLTests, ReadEntireStreams) -{ - // Test with local file - URL fileUrl (testFile); - - // readEntireBinaryStream - MemoryBlock binaryData; - bool success = fileUrl.readEntireBinaryStream (binaryData, false); - EXPECT_TRUE (success); - EXPECT_EQ (binaryData.getSize(), testFile.getSize()); - EXPECT_EQ (String::fromUTF8 ((const char*) binaryData.getData(), (int) binaryData.getSize()), "Test content"); - - // readEntireTextStream - String textData = fileUrl.readEntireTextStream (false); - EXPECT_EQ (textData, "Test content"); - - // Test with POST flag - String textDataPost = fileUrl.readEntireTextStream (true); - EXPECT_EQ (textDataPost, "Test content"); - - // readEntireXmlStream with valid XML - File xmlFile = testDir.getChildFile ("test.xml"); - xmlFile.replaceWithText ("value"); - URL xmlUrl (xmlFile); - - auto xmlDoc = xmlUrl.readEntireXmlStream (false); - EXPECT_NE (xmlDoc, nullptr); - if (xmlDoc != nullptr) - { - EXPECT_EQ (xmlDoc->getTagName(), "root"); - auto* element = xmlDoc->getChildByName ("element"); - EXPECT_NE (element, nullptr); - if (element != nullptr) - EXPECT_EQ (element->getAllSubText(), "value"); - } -} -#endif - TEST_F (URLTests, LaunchInDefaultBrowser) { /* @@ -804,42 +709,6 @@ TEST_F (URLTests, DownloadTaskOptions) EXPECT_TRUE (chained.usePost); } -#if ! YUP_WASM -TEST_F (URLTests, DownloadTask) -{ - // Create a test file to serve as download source - File sourceFile = testDir.getChildFile ("source.txt"); - sourceFile.replaceWithText ("Download content"); - - URL sourceUrl (sourceFile); - File targetFile = testDir.getChildFile ("downloaded.txt"); - - // Test basic download - URL::DownloadTaskOptions options; - auto task = sourceUrl.downloadToFile (targetFile, options); - - if (task != nullptr) - { - // Wait for download to complete - int maxWait = 50; // 5 seconds max - while (! task->isFinished() && maxWait-- > 0) - Thread::sleep (100); - - EXPECT_TRUE (task->isFinished()); - EXPECT_FALSE (task->hadError()); - EXPECT_GT (task->getTotalLength(), 0); - EXPECT_EQ (task->getLengthDownloaded(), task->getTotalLength()); - EXPECT_EQ (task->getTargetLocation().getFullPathName(), targetFile.getFullPathName()); - - // Verify downloaded content - if (targetFile.existsAsFile()) - { - EXPECT_EQ (targetFile.loadFileAsString(), "Download content"); - } - } -} -#endif - TEST_F (URLTests, URLWithComplexEscaping) { // Test various escape sequences in different parts of URL @@ -1004,3 +873,130 @@ TEST_F (URLTests, DataURLs) URL imageUrl ("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=="); EXPECT_TRUE (imageUrl.isWellFormed()); } + +#if ! YUP_WASM +TEST_F (URLTests, LocalFileStreams) +{ + URL fileUrl (testFile); + + // Test input stream + if (auto inputStream = fileUrl.createInputStream (URL::InputStreamOptions (URL::ParameterHandling::inAddress))) + { + EXPECT_EQ (inputStream->getTotalLength(), testFile.getSize()); + + MemoryBlock readData; + inputStream->readIntoMemoryBlock (readData); + EXPECT_EQ (readData.toString(), "Test content"); + } + + // Test output stream + File tempFile = File::getSpecialLocation (File::tempDirectory).getChildFile ("test_output.txt"); + URL outputUrl (tempFile); + + if (auto outputStream = outputUrl.createOutputStream()) + { + outputStream->writeText ("Test output", false, false, nullptr); + outputStream.reset(); + + EXPECT_TRUE (tempFile.existsAsFile()); + EXPECT_EQ (tempFile.loadFileAsString(), "Test output"); + tempFile.deleteFile(); + } + + // Test readEntireBinaryStream + MemoryBlock binaryData; + EXPECT_TRUE (fileUrl.readEntireBinaryStream (binaryData, false)); + EXPECT_EQ (binaryData.toString(), "Test content"); + + // Test readEntireTextStream + String textData = fileUrl.readEntireTextStream (false); + EXPECT_EQ (textData, "Test content"); + + // Test readEntireXmlStream with XML content + File xmlFile = File::getSpecialLocation (File::tempDirectory).getChildFile ("test.xml"); + xmlFile.replaceWithText ("content"); + URL xmlUrl (xmlFile); + + if (auto xml = xmlUrl.readEntireXmlStream (false)) + { + EXPECT_EQ (xml->getTagName(), "root"); + if (auto child = xml->getChildByName ("child")) + { + EXPECT_EQ (child->getStringAttribute ("attr"), "value"); + EXPECT_EQ (child->getAllSubText(), "content"); + } + } + xmlFile.deleteFile(); +} + +TEST_F (URLTests, ReadEntireStreams) +{ + // Test with local file + URL fileUrl (testFile); + + // readEntireBinaryStream + MemoryBlock binaryData; + bool success = fileUrl.readEntireBinaryStream (binaryData, false); + EXPECT_TRUE (success); + EXPECT_EQ (binaryData.getSize(), testFile.getSize()); + EXPECT_EQ (String::fromUTF8 ((const char*) binaryData.getData(), (int) binaryData.getSize()), "Test content"); + + // readEntireTextStream + String textData = fileUrl.readEntireTextStream (false); + EXPECT_EQ (textData, "Test content"); + + // Test with POST flag + String textDataPost = fileUrl.readEntireTextStream (true); + EXPECT_EQ (textDataPost, "Test content"); + + // readEntireXmlStream with valid XML + File xmlFile = testDir.getChildFile ("test.xml"); + xmlFile.replaceWithText ("value"); + URL xmlUrl (xmlFile); + + auto xmlDoc = xmlUrl.readEntireXmlStream (false); + EXPECT_NE (xmlDoc, nullptr); + if (xmlDoc != nullptr) + { + EXPECT_EQ (xmlDoc->getTagName(), "root"); + auto* element = xmlDoc->getChildByName ("element"); + EXPECT_NE (element, nullptr); + if (element != nullptr) + EXPECT_EQ (element->getAllSubText(), "value"); + } +} + +TEST_F (URLTests, DownloadTask) +{ + // Create a test file to serve as download source + File sourceFile = testDir.getChildFile ("source.txt"); + sourceFile.replaceWithText ("Download content"); + + URL sourceUrl (sourceFile); + File targetFile = testDir.getChildFile ("downloaded.txt"); + + // Test basic download + URL::DownloadTaskOptions options; + auto task = sourceUrl.downloadToFile (targetFile, options); + + if (task != nullptr) + { + // Wait for download to complete + int maxWait = 50; // 5 seconds max + while (! task->isFinished() && maxWait-- > 0) + Thread::sleep (100); + + EXPECT_TRUE (task->isFinished()); + EXPECT_FALSE (task->hadError()); + EXPECT_GT (task->getTotalLength(), 0); + EXPECT_EQ (task->getLengthDownloaded(), task->getTotalLength()); + EXPECT_EQ (task->getTargetLocation().getFullPathName(), targetFile.getFullPathName()); + + // Verify downloaded content + if (targetFile.existsAsFile()) + { + EXPECT_EQ (targetFile.loadFileAsString(), "Download content"); + } + } +} +#endif From 2e3937bddd6494807433f19aa39ac2e8e63a8eb0 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Sun, 7 Dec 2025 15:12:54 +0100 Subject: [PATCH 06/30] Move to C++20 by default --- README.md | 2 +- cmake/yup_dependencies.cmake | 2 +- cmake/yup_modules.cmake | 4 ++-- cmake/yup_platforms.cmake | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 33bfc4b9f..15226d3d8 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ YUP brings a suite of powerful features, including: ## Prerequisites Before building, ensure you have a: -- C++17-compliant compiler +- C++20-compliant compiler - CMake 3.28 or later diff --git a/cmake/yup_dependencies.cmake b/cmake/yup_dependencies.cmake index df7b29848..2fa317996 100644 --- a/cmake/yup_dependencies.cmake +++ b/cmake/yup_dependencies.cmake @@ -89,7 +89,7 @@ function (_yup_fetch_perfetto) FetchContent_MakeAvailable (Perfetto) add_library (perfetto STATIC) - target_compile_features (perfetto PUBLIC cxx_std_17) + target_compile_features (perfetto PUBLIC cxx_std_20) target_sources (perfetto PRIVATE "$" diff --git a/cmake/yup_modules.cmake b/cmake/yup_modules.cmake index d313a8be2..a73f8cfe6 100644 --- a/cmake/yup_modules.cmake +++ b/cmake/yup_modules.cmake @@ -183,7 +183,7 @@ function (_yup_module_setup_target module_name if (module_cpp_standard) target_compile_features (${module_name} INTERFACE cxx_std_${module_cpp_standard}) else() - target_compile_features (${module_name} INTERFACE cxx_std_17) + target_compile_features (${module_name} INTERFACE cxx_std_20) endif() set_target_properties (${module_name} PROPERTIES @@ -366,7 +366,7 @@ function (yup_add_module module_path modules_definitions module_group) endif() endforeach() - _yup_set_default (module_cpp_standard "17") + _yup_set_default (module_cpp_standard "20") _yup_set_default (module_arc_enabled OFF) _yup_set_default (module_needs_python OFF) _yup_resolve_variable_paths ("${module_searchpaths}" module_searchpaths) diff --git a/cmake/yup_platforms.cmake b/cmake/yup_platforms.cmake index f8db200b1..47d8ffdcc 100644 --- a/cmake/yup_platforms.cmake +++ b/cmake/yup_platforms.cmake @@ -37,7 +37,7 @@ function (_yup_prepare_gradle_android) _yup_set_default (YUP_ANDROID_TOOLCHAIN "clang") _yup_set_default (YUP_ANDROID_PLATFORM "android-${YUP_ANDROID_MIN_SDK_VERSION}") _yup_set_default (YUP_ANDROID_STL "c++_shared") - _yup_set_default (YUP_ANDROID_CPP_VERSION "17") + _yup_set_default (YUP_ANDROID_CPP_VERSION "20") _yup_set_default (YUP_ANDROID_APPLICATION_NAMESPACE "org.yup") _yup_set_default (YUP_ANDROID_APPLICATION_ID "org.yup.default_app") _yup_set_default (YUP_ANDROID_APPLICATION_VERSION "1.0") From 320590b2264435ac8712be3836c3d5b53f804f71 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Sun, 7 Dec 2025 15:33:53 +0100 Subject: [PATCH 07/30] Get rid of Optional and switch to C++20 --- .../audio_play_head/yup_AudioPlayHead.h | 60 +- .../yup_audio_basics/midi/yup_MidiFile.cpp | 26 +- .../midi/yup_MidiMessageSequence.cpp | 18 +- .../native/yup_Audio_ios.cpp | 2 +- .../clap/yup_audio_plugin_client_CLAP.cpp | 2 +- modules/yup_core/containers/yup_Optional.h | 238 ------- .../yup_core/native/yup_SharedCode_posix.h | 4 +- modules/yup_core/yup_core.h | 1 - python/pyproject.toml | 3 + tests/yup_audio_basics/yup_AudioPlayHead.cpp | 52 +- tests/yup_audio_basics/yup_MidiFile.cpp | 10 +- tests/yup_core/yup_Optional.cpp | 637 ------------------ thirdparty/rive_decoders/rive_decoders.h | 2 +- 13 files changed, 91 insertions(+), 964 deletions(-) delete mode 100644 modules/yup_core/containers/yup_Optional.h delete mode 100644 tests/yup_core/yup_Optional.cpp diff --git a/modules/yup_audio_basics/audio_play_head/yup_AudioPlayHead.h b/modules/yup_audio_basics/audio_play_head/yup_AudioPlayHead.h index 051b30c89..87e1d3c3a 100644 --- a/modules/yup_audio_basics/audio_play_head/yup_AudioPlayHead.h +++ b/modules/yup_audio_basics/audio_play_head/yup_AudioPlayHead.h @@ -361,43 +361,43 @@ class YUP_API AudioPlayHead { public: /** Returns the number of samples that have elapsed. */ - Optional getTimeInSamples() const { return getOptional (flagTimeSamples, timeInSamples); } + std::optional getTimeInSamples() const { return getOptional (flagTimeSamples, timeInSamples); } /** @see getTimeInSamples() */ - void setTimeInSamples (Optional timeInSamplesIn) { setOptional (flagTimeSamples, timeInSamples, timeInSamplesIn); } + void setTimeInSamples (std::optional timeInSamplesIn) { setOptional (flagTimeSamples, timeInSamples, timeInSamplesIn); } /** Returns the number of seconds that have elapsed. */ - Optional getTimeInSeconds() const { return getOptional (flagTimeSeconds, timeInSeconds); } + std::optional getTimeInSeconds() const { return getOptional (flagTimeSeconds, timeInSeconds); } /** @see getTimeInSamples() */ - void setTimeInSeconds (Optional timeInSecondsIn) { setOptional (flagTimeSeconds, timeInSeconds, timeInSecondsIn); } + void setTimeInSeconds (std::optional timeInSecondsIn) { setOptional (flagTimeSeconds, timeInSeconds, timeInSecondsIn); } /** Returns the bpm, if available. */ - Optional getBpm() const { return getOptional (flagTempo, tempoBpm); } + std::optional getBpm() const { return getOptional (flagTempo, tempoBpm); } /** @see getBpm() */ - void setBpm (Optional bpmIn) { setOptional (flagTempo, tempoBpm, bpmIn); } + void setBpm (std::optional bpmIn) { setOptional (flagTempo, tempoBpm, bpmIn); } /** Returns the time signature, if available. */ - Optional getTimeSignature() const { return getOptional (flagTimeSignature, timeSignature); } + std::optional getTimeSignature() const { return getOptional (flagTimeSignature, timeSignature); } /** @see getTimeSignature() */ - void setTimeSignature (Optional timeSignatureIn) { setOptional (flagTimeSignature, timeSignature, timeSignatureIn); } + void setTimeSignature (std::optional timeSignatureIn) { setOptional (flagTimeSignature, timeSignature, timeSignatureIn); } /** Returns host loop points, if available. */ - Optional getLoopPoints() const { return getOptional (flagLoopPoints, loopPoints); } + std::optional getLoopPoints() const { return getOptional (flagLoopPoints, loopPoints); } /** @see getLoopPoints() */ - void setLoopPoints (Optional loopPointsIn) { setOptional (flagLoopPoints, loopPoints, loopPointsIn); } + void setLoopPoints (std::optional loopPointsIn) { setOptional (flagLoopPoints, loopPoints, loopPointsIn); } /** The number of bars since the beginning of the timeline. This value isn't available in all hosts or in all plugin formats. */ - Optional getBarCount() const { return getOptional (flagBarCount, barCount); } + std::optional getBarCount() const { return getOptional (flagBarCount, barCount); } /** @see getBarCount() */ - void setBarCount (Optional barCountIn) { setOptional (flagBarCount, barCount, barCountIn); } + void setBarCount (std::optional barCountIn) { setOptional (flagBarCount, barCount, barCountIn); } /** The position of the start of the last bar, in units of quarter-notes. @@ -406,40 +406,40 @@ class YUP_API AudioPlayHead Note - this value may be unavailable on some hosts, e.g. Pro-Tools. */ - Optional getPpqPositionOfLastBarStart() const { return getOptional (flagLastBarStartPpq, lastBarStartPpq); } + std::optional getPpqPositionOfLastBarStart() const { return getOptional (flagLastBarStartPpq, lastBarStartPpq); } /** @see getPpqPositionOfLastBarStart() */ - void setPpqPositionOfLastBarStart (Optional positionIn) { setOptional (flagLastBarStartPpq, lastBarStartPpq, positionIn); } + void setPpqPositionOfLastBarStart (std::optional positionIn) { setOptional (flagLastBarStartPpq, lastBarStartPpq, positionIn); } /** The video frame rate, if available. */ - Optional getFrameRate() const { return getOptional (flagFrameRate, frame); } + std::optional getFrameRate() const { return getOptional (flagFrameRate, frame); } /** @see getFrameRate() */ - void setFrameRate (Optional frameRateIn) { setOptional (flagFrameRate, frame, frameRateIn); } + void setFrameRate (std::optional frameRateIn) { setOptional (flagFrameRate, frame, frameRateIn); } /** The current play position, in units of quarter-notes. */ - Optional getPpqPosition() const { return getOptional (flagPpqPosition, positionPpq); } + std::optional getPpqPosition() const { return getOptional (flagPpqPosition, positionPpq); } /** @see getPpqPosition() */ - void setPpqPosition (Optional ppqPositionIn) { setOptional (flagPpqPosition, positionPpq, ppqPositionIn); } + void setPpqPosition (std::optional ppqPositionIn) { setOptional (flagPpqPosition, positionPpq, ppqPositionIn); } /** For timecode, the position of the start of the timeline, in seconds from 00:00:00:00. */ - Optional getEditOriginTime() const { return getOptional (flagOriginTime, originTime); } + std::optional getEditOriginTime() const { return getOptional (flagOriginTime, originTime); } /** @see getEditOriginTime() */ - void setEditOriginTime (Optional editOriginTimeIn) { setOptional (flagOriginTime, originTime, editOriginTimeIn); } + void setEditOriginTime (std::optional editOriginTimeIn) { setOptional (flagOriginTime, originTime, editOriginTimeIn); } /** Get the host's callback time in nanoseconds, if available. */ - Optional getHostTimeNs() const { return getOptional (flagHostTimeNs, hostTimeNs); } + std::optional getHostTimeNs() const { return getOptional (flagHostTimeNs, hostTimeNs); } /** @see getHostTimeNs() */ - void setHostTimeNs (Optional hostTimeNsIn) { setOptional (flagHostTimeNs, hostTimeNs, hostTimeNsIn); } + void setHostTimeNs (std::optional hostTimeNsIn) { setOptional (flagHostTimeNs, hostTimeNs, hostTimeNsIn); } /** The current play position, in samples from the start of processing, without looping, if available. */ - Optional getContinuousTimeInSamples() const { return getOptional (flagContinuousTime, continuousTimeInSamples); } + std::optional getContinuousTimeInSamples() const { return getOptional (flagContinuousTime, continuousTimeInSamples); } /** @see getContinuousTimeInSamples() */ - void setContinuousTimeInSamples (Optional cont) { setOptional (flagContinuousTime, continuousTimeInSamples, cont); } + void setContinuousTimeInSamples (std::optional cont) { setOptional (flagContinuousTime, continuousTimeInSamples, cont); } /** True if the transport is currently playing. */ bool getIsPlaying() const { return getFlag (flagIsPlaying); } @@ -502,18 +502,18 @@ class YUP_API AudioPlayHead } template - Optional getOptional (int64_t flagToCheck, Value value) const + std::optional getOptional (int64_t flagToCheck, Value value) const { - return getFlag (flagToCheck) ? makeOptional (std::move (value)) : nullopt; + return getFlag (flagToCheck) ? std::make_optional (std::move (value)) : std::nullopt; } template - void setOptional (int64_t flagToCheck, Value& value, Optional opt) + void setOptional (int64_t flagToCheck, Value& value, std::optional opt) { - if (opt.hasValue()) + if (opt.has_value()) value = *opt; - setFlag (flagToCheck, opt.hasValue()); + setFlag (flagToCheck, opt.has_value()); } enum @@ -564,7 +564,7 @@ class YUP_API AudioPlayHead in which a time would make sense, and some hosts will almost certainly have multithreading issues if it's not called on the audio thread. */ - virtual Optional getPosition() const = 0; + virtual std::optional getPosition() const = 0; /** Returns true if this object can control the transport. */ virtual bool canControlTransport(); diff --git a/modules/yup_audio_basics/midi/yup_MidiFile.cpp b/modules/yup_audio_basics/midi/yup_MidiFile.cpp index 4452c348b..232b547eb 100644 --- a/modules/yup_audio_basics/midi/yup_MidiFile.cpp +++ b/modules/yup_audio_basics/midi/yup_MidiFile.cpp @@ -79,7 +79,7 @@ struct ReadTrait }; template -Optional tryRead (const uint8*& data, size_t& remaining) +std::optional tryRead (const uint8*& data, size_t& remaining) { using Trait = ReadTrait; constexpr auto size = sizeof (Integral); @@ -87,7 +87,7 @@ Optional tryRead (const uint8*& data, size_t& remaining) if (remaining < size) return {}; - const Optional result { Trait::read (data) }; + const std::optional result { Trait::read (data) }; data += size; remaining -= size; @@ -103,15 +103,15 @@ struct HeaderDetails short numberOfTracks = 0; }; -static Optional parseMidiHeader (const uint8* const initialData, - const size_t maxSize) +static std::optional parseMidiHeader (const uint8* const initialData, + const size_t maxSize) { auto* data = initialData; auto remaining = maxSize; auto ch = tryRead (data, remaining); - if (! ch.hasValue()) + if (! ch.has_value()) return {}; if (*ch != ByteOrder::bigEndianInt ("MThd")) @@ -124,7 +124,7 @@ static Optional parseMidiHeader (const uint8* const initialData, { ch = tryRead (data, remaining); - if (! ch.hasValue()) + if (! ch.has_value()) return {}; if (*ch == ByteOrder::bigEndianInt ("MThd")) @@ -141,22 +141,22 @@ static Optional parseMidiHeader (const uint8* const initialData, const auto bytesRemaining = tryRead (data, remaining); - if (! bytesRemaining.hasValue() || *bytesRemaining > remaining) + if (! bytesRemaining.has_value() || *bytesRemaining > remaining) return {}; const auto optFileType = tryRead (data, remaining); - if (! optFileType.hasValue() || 2 < *optFileType) + if (! optFileType.has_value() || 2 < *optFileType) return {}; const auto optNumTracks = tryRead (data, remaining); - if (! optNumTracks.hasValue() || (*optFileType == 0 && *optNumTracks != 1)) + if (! optNumTracks.has_value() || (*optFileType == 0 && *optNumTracks != 1)) return {}; const auto optTimeFormat = tryRead (data, remaining); - if (! optTimeFormat.hasValue()) + if (! optTimeFormat.has_value()) return {}; HeaderDetails result; @@ -403,7 +403,7 @@ bool MidiFile::readFrom (InputStream& sourceStream, const auto optHeader = MidiFileHelpers::parseMidiHeader (d, size); - if (! optHeader.hasValue()) + if (! optHeader.has_value()) return false; const auto header = *optHeader; @@ -416,12 +416,12 @@ bool MidiFile::readFrom (InputStream& sourceStream, { const auto optChunkType = MidiFileHelpers::tryRead (d, size); - if (! optChunkType.hasValue()) + if (! optChunkType.has_value()) return false; const auto optChunkSize = MidiFileHelpers::tryRead (d, size); - if (! optChunkSize.hasValue()) + if (! optChunkSize.has_value()) return false; const auto chunkSize = *optChunkSize; diff --git a/modules/yup_audio_basics/midi/yup_MidiMessageSequence.cpp b/modules/yup_audio_basics/midi/yup_MidiMessageSequence.cpp index fb9ce8a47..9d7f88b0c 100644 --- a/modules/yup_audio_basics/midi/yup_MidiMessageSequence.cpp +++ b/modules/yup_audio_basics/midi/yup_MidiMessageSequence.cpp @@ -335,12 +335,12 @@ void MidiMessageSequence::deleteSysExMessages() //============================================================================== class OptionalPitchWheel { - Optional value; + std::optional value; public: void emit (int channel, Array& out) const { - if (value.hasValue()) + if (value.has_value()) out.add (MidiMessage::pitchWheel (channel, *value)); } @@ -352,13 +352,13 @@ class OptionalPitchWheel class OptionalControllerValues { - Optional values[128]; + std::optional values[128]; public: void emit (int channel, Array& out) const { for (auto it = std::begin (values); it != std::end (values); ++it) - if (it->hasValue()) + if (it->has_value()) out.add (MidiMessage::controllerEvent (channel, (int) std::distance (std::begin (values), it), **it)); } @@ -370,15 +370,15 @@ class OptionalControllerValues class OptionalProgramChange { - Optional value, bankLSB, bankMSB; + std::optional value, bankLSB, bankMSB; public: void emit (int channel, double time, Array& out) const { - if (! value.hasValue()) + if (! value.has_value()) return; - if (bankLSB.hasValue() && bankMSB.hasValue()) + if (bankLSB.has_value() && bankMSB.has_value()) { out.add (MidiMessage::controllerEvent (channel, 0x00, *bankMSB).withTimeStamp (time)); out.add (MidiMessage::controllerEvent (channel, 0x20, *bankLSB).withTimeStamp (time)); @@ -414,7 +414,7 @@ class ParameterNumberState nrpn }; - Optional newestRpnLsb, newestRpnMsb, newestNrpnLsb, newestNrpnMsb, lastSentLsb, lastSentMsb; + std::optional newestRpnLsb, newestRpnMsb, newestNrpnLsb, newestNrpnMsb, lastSentLsb, lastSentMsb; Kind lastSentKind = Kind::rpn, newestKind = Kind::rpn; public: @@ -430,7 +430,7 @@ class ParameterNumberState auto lastSent = std::tie (lastSentKind, lastSentMsb, lastSentLsb); const auto newest = std::tie (newestKind, newestMsb, newestLsb); - if (lastSent == newest || ! newestMsb.hasValue() || ! newestLsb.hasValue()) + if (lastSent == newest || ! newestMsb.has_value() || ! newestLsb.has_value()) return; out.add (MidiMessage::controllerEvent (channel, newestKind == Kind::rpn ? 0x65 : 0x63, *newestMsb).withTimeStamp (time)); diff --git a/modules/yup_audio_devices/native/yup_Audio_ios.cpp b/modules/yup_audio_devices/native/yup_Audio_ios.cpp index d5fd339b2..093597ed4 100644 --- a/modules/yup_audio_devices/native/yup_Audio_ios.cpp +++ b/modules/yup_audio_devices/native/yup_Audio_ios.cpp @@ -658,7 +658,7 @@ struct iOSAudioIODevice::Pimpl final : public AsyncUpdater impl.handleAudioTransportEvent (kAudioUnitRemoteControlEvent_Rewind); } - Optional getPosition() const override + std::optional getPosition() const override { if (! canControlTransportImpl()) return {}; diff --git a/modules/yup_audio_plugin_client/clap/yup_audio_plugin_client_CLAP.cpp b/modules/yup_audio_plugin_client/clap/yup_audio_plugin_client_CLAP.cpp index d39d4c799..bb24117d0 100644 --- a/modules/yup_audio_plugin_client/clap/yup_audio_plugin_client_CLAP.cpp +++ b/modules/yup_audio_plugin_client/clap/yup_audio_plugin_client_CLAP.cpp @@ -227,7 +227,7 @@ class AudioPluginPlayHeadCLAP final : public AudioPlayHead return; } - Optional getPosition() const override + std::optional getPosition() const override { if (process.transport == nullptr) return {}; diff --git a/modules/yup_core/containers/yup_Optional.h b/modules/yup_core/containers/yup_Optional.h deleted file mode 100644 index 67447e084..000000000 --- a/modules/yup_core/containers/yup_Optional.h +++ /dev/null @@ -1,238 +0,0 @@ -/* - ============================================================================== - - This file is part of the YUP library. - Copyright (c) 2024 - kunitoki@gmail.com - - YUP is an open source library subject to open-source licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - to use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - YUP IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace yup -{ - -using Nullopt = std::nullopt_t; -constexpr auto nullopt = std::nullopt; - -// Without this, our tests can emit "unreachable code" warnings during -// link time code generation. -YUP_BEGIN_IGNORE_WARNINGS_MSVC (4702) - -#ifndef DOXYGEN -#define YUP_OPTIONAL_OPERATORS X (==) X (!=) X (<) X (<=) X (>) X (>=) -#endif - -/** - A simple optional type. - - In new code, you should probably prefer using std::optional directly. - - This is intended to stand-in for std::optional while YUP's minimum - supported language standard is lower than C++17. When the minimum language - standard moves to C++17, this class will probably be deprecated, in much - the same way that yup::ScopedPointer was deprecated in favour of - std::unique_ptr after C++11. - - This isn't really intended to be used by YUP clients. Instead, it's to be - used internally in YUP code, with an API close-enough to std::optional - that the types can be swapped with fairly minor disruption at some point in - the future, but *without breaking any public APIs*. - - @tags{Core} -*/ -template -class Optional -{ - template - struct IsOptional : std::false_type - { - }; - - template - struct IsOptional> : std::true_type - { - }; - -public: - Optional() = default; - Optional (const Optional&) = default; - Optional (Optional&&) = default; - Optional& operator= (const Optional&) = default; - Optional& operator= (Optional&&) = default; - - Optional (Nullopt) noexcept {} - - template >::value, int> = 0> - Optional (Head&& head, Tail&&... tail) noexcept (std::is_nothrow_constructible_v, Head, Tail...>) - : opt (std::forward (head), std::forward (tail)...) - { - } - - template - Optional (const Optional& other) noexcept (std::is_nothrow_constructible_v, const std::optional&>) - : opt (other.opt) - { - } - - template - Optional (Optional&& other) noexcept (std::is_nothrow_constructible_v, std::optional&&>) - : opt (std::move (other.opt)) - { - } - - template >::value, int> = 0> - Optional& operator= (Other&& other) noexcept (std::is_nothrow_assignable_v, Other>) - { - opt = std::forward (other); - return *this; - } - - template - Optional& operator= (const Optional& other) noexcept (std::is_nothrow_assignable_v, const std::optional&>) - { - opt = other.opt; - return *this; - } - - template - Optional& operator= (Optional&& other) noexcept (std::is_nothrow_assignable_v, std::optional&&>) - { - opt = std::move (other.opt); - return *this; - } - - template - auto& emplace (Other&&... other) - { - return opt.emplace (std::forward (other)...); - } - - void reset() noexcept - { - opt.reset(); - } - - void swap (Optional& other) noexcept (std::is_nothrow_swappable_v>) - { - opt.swap (other.opt); - } - - decltype (auto) operator->() { return opt.operator->(); } - - decltype (auto) operator->() const { return opt.operator->(); } - - decltype (auto) operator*() { return opt.operator*(); } - - decltype (auto) operator*() const { return opt.operator*(); } - - explicit operator bool() const noexcept { return opt.has_value(); } - - bool hasValue() const noexcept { return opt.has_value(); } - - template - decltype (auto) orFallback (U&& fallback) const& - { - return opt.value_or (std::forward (fallback)); - } - - template - decltype (auto) orFallback (U&& fallback) & - { - return opt.value_or (std::forward (fallback)); - } - -#define X(op) \ - template \ - friend bool operator op (const Optional&, const Optional&); \ - template \ - friend bool operator op (const Optional&, Nullopt); \ - template \ - friend bool operator op (Nullopt, const Optional&); \ - template \ - friend bool operator op (const Optional&, const U&); \ - template \ - friend bool operator op (const T&, const Optional&); - - YUP_OPTIONAL_OPERATORS - -#undef X - -private: - template - friend class Optional; - - std::optional opt; -}; - -YUP_END_IGNORE_WARNINGS_MSVC - -template -Optional> makeOptional (Value&& v) -{ - return std::forward (v); -} - -#ifndef DOXYGEN -#define X(op) \ - template \ - bool operator op (const Optional& lhs, const Optional& rhs) \ - { \ - return lhs.opt op rhs.opt; \ - } \ - template \ - bool operator op (const Optional& lhs, Nullopt rhs) \ - { \ - return lhs.opt op rhs; \ - } \ - template \ - bool operator op (Nullopt lhs, const Optional& rhs) \ - { \ - return lhs op rhs.opt; \ - } \ - template \ - bool operator op (const Optional& lhs, const U& rhs) \ - { \ - return lhs.opt op rhs; \ - } \ - template \ - bool operator op (const T& lhs, const Optional& rhs) \ - { \ - return lhs op rhs.opt; \ - } - -YUP_OPTIONAL_OPERATORS - -#undef X -#undef YUP_OPTIONAL_OPERATORS -#endif - -} // namespace yup diff --git a/modules/yup_core/native/yup_SharedCode_posix.h b/modules/yup_core/native/yup_SharedCode_posix.h index 84f10da0c..9a89db760 100644 --- a/modules/yup_core/native/yup_SharedCode_posix.h +++ b/modules/yup_core/native/yup_SharedCode_posix.h @@ -993,10 +993,10 @@ class PosixSchedulerPriority return { scheduler, param.sched_priority }; } - static PosixSchedulerPriority getNativeSchedulerAndPriority (const Optional& rt, + static PosixSchedulerPriority getNativeSchedulerAndPriority (const std::optional& rt, [[maybe_unused]] Thread::Priority prio) { - const auto isRealtime = rt.hasValue(); + const auto isRealtime = rt.has_value(); const auto priority = [&] { diff --git a/modules/yup_core/yup_core.h b/modules/yup_core/yup_core.h index 647cc4b63..2605b8eaa 100644 --- a/modules/yup_core/yup_core.h +++ b/modules/yup_core/yup_core.h @@ -276,7 +276,6 @@ YUP_END_IGNORE_WARNINGS_MSVC #include "memory/yup_ReferenceCountedObject.h" #include "memory/yup_ScopedPointer.h" #include "memory/yup_OptionalScopedPointer.h" -#include "containers/yup_Optional.h" #include "containers/yup_Enumerate.h" #include "containers/yup_ScopedValueSetter.h" #include "memory/yup_Singleton.h" diff --git a/python/pyproject.toml b/python/pyproject.toml index 5b3d77bba..c971107d2 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -29,6 +29,9 @@ manylinux-i686-image = "manylinux_2_28" manylinux-x86_64-image = "manylinux_2_28" manylinux-aarch64-image = "manylinux_2_28" +[tool.cibuildwheel.windows] +environment = { "CMAKE_GENERATOR" = "Visual Studio 17 2022", "CMAKE_CXX_STANDARD" = "20" } + [tool.cibuildwheel.linux] before-build = [ """dnf install -y zlib-devel openssl-devel freetype-devel fontconfig-devel freeglut-devel alsa-lib-devel mesa-libGL-devel \ diff --git a/tests/yup_audio_basics/yup_AudioPlayHead.cpp b/tests/yup_audio_basics/yup_AudioPlayHead.cpp index c63b60c56..e31ff7422 100644 --- a/tests/yup_audio_basics/yup_AudioPlayHead.cpp +++ b/tests/yup_audio_basics/yup_AudioPlayHead.cpp @@ -31,7 +31,7 @@ namespace class TestAudioPlayHead : public AudioPlayHead { public: - Optional getPosition() const override + std::optional getPosition() const override { return testPosition; } @@ -43,11 +43,11 @@ class TestAudioPlayHead : public AudioPlayHead void clearTestPosition() { - testPosition = nullopt; + testPosition = std::nullopt; } private: - Optional testPosition; + std::optional testPosition; }; } // namespace @@ -239,18 +239,18 @@ TEST_F (AudioPlayHeadTests, PositionInfoGettersReturnNulloptByDefault) { AudioPlayHead::PositionInfo info; - EXPECT_FALSE (info.getTimeInSamples().hasValue()); - EXPECT_FALSE (info.getTimeInSeconds().hasValue()); - EXPECT_FALSE (info.getBpm().hasValue()); - EXPECT_FALSE (info.getTimeSignature().hasValue()); - EXPECT_FALSE (info.getLoopPoints().hasValue()); - EXPECT_FALSE (info.getBarCount().hasValue()); - EXPECT_FALSE (info.getPpqPositionOfLastBarStart().hasValue()); - EXPECT_FALSE (info.getFrameRate().hasValue()); - EXPECT_FALSE (info.getPpqPosition().hasValue()); - EXPECT_FALSE (info.getEditOriginTime().hasValue()); - EXPECT_FALSE (info.getHostTimeNs().hasValue()); - EXPECT_FALSE (info.getContinuousTimeInSamples().hasValue()); + EXPECT_FALSE (info.getTimeInSamples().has_value()); + EXPECT_FALSE (info.getTimeInSeconds().has_value()); + EXPECT_FALSE (info.getBpm().has_value()); + EXPECT_FALSE (info.getTimeSignature().has_value()); + EXPECT_FALSE (info.getLoopPoints().has_value()); + EXPECT_FALSE (info.getBarCount().has_value()); + EXPECT_FALSE (info.getPpqPositionOfLastBarStart().has_value()); + EXPECT_FALSE (info.getFrameRate().has_value()); + EXPECT_FALSE (info.getPpqPosition().has_value()); + EXPECT_FALSE (info.getEditOriginTime().has_value()); + EXPECT_FALSE (info.getHostTimeNs().has_value()); + EXPECT_FALSE (info.getContinuousTimeInSamples().has_value()); // Boolean flags should default to false EXPECT_FALSE (info.getIsPlaying()); @@ -264,18 +264,18 @@ TEST_F (AudioPlayHeadTests, PositionInfoSettersAndGetters) // Test setTimeInSamples/getTimeInSamples info.setTimeInSamples (1000); - EXPECT_TRUE (info.getTimeInSamples().hasValue()); + EXPECT_TRUE (info.getTimeInSamples().has_value()); EXPECT_EQ (1000, *info.getTimeInSamples()); // Test setBpm/getBpm info.setBpm (120.0); - EXPECT_TRUE (info.getBpm().hasValue()); + EXPECT_TRUE (info.getBpm().has_value()); EXPECT_EQ (120.0, *info.getBpm()); // Test setTimeSignature/getTimeSignature AudioPlayHead::TimeSignature sig { 3, 4 }; info.setTimeSignature (sig); - EXPECT_TRUE (info.getTimeSignature().hasValue()); + EXPECT_TRUE (info.getTimeSignature().has_value()); EXPECT_EQ (sig, *info.getTimeSignature()); // Test boolean flags @@ -294,18 +294,18 @@ TEST_F (AudioPlayHeadTests, PositionInfoSetOptionalValues) AudioPlayHead::PositionInfo info; // Set values using Optional - info.setTimeInSamples (makeOptional (2000)); - EXPECT_TRUE (info.getTimeInSamples().hasValue()); + info.setTimeInSamples (std::optional (2000)); + EXPECT_TRUE (info.getTimeInSamples().has_value()); EXPECT_EQ (2000, *info.getTimeInSamples()); // Clear values using nullopt - info.setTimeInSamples (nullopt); - EXPECT_FALSE (info.getTimeInSamples().hasValue()); + info.setTimeInSamples (std::nullopt); + EXPECT_FALSE (info.getTimeInSamples().has_value()); // Test with FrameRate AudioPlayHead::FrameRate rate (AudioPlayHead::FrameRateType::fps30); info.setFrameRate (rate); - EXPECT_TRUE (info.getFrameRate().hasValue()); + EXPECT_TRUE (info.getFrameRate().has_value()); EXPECT_EQ (rate, *info.getFrameRate()); } @@ -330,7 +330,7 @@ TEST_F (AudioPlayHeadTests, PlayHeadPositionReturnsNulloptByDefault) { playHead->clearTestPosition(); auto position = playHead->getPosition(); - EXPECT_FALSE (position.hasValue()); + EXPECT_FALSE (position.has_value()); } TEST_F (AudioPlayHeadTests, PlayHeadCanReturnPositionInfo) @@ -343,6 +343,6 @@ TEST_F (AudioPlayHeadTests, PlayHeadCanReturnPositionInfo) playHead->setTestPosition (testInfo); auto position = playHead->getPosition(); - EXPECT_TRUE (position.hasValue()); + EXPECT_TRUE (position.has_value()); EXPECT_EQ (testInfo, *position); -} \ No newline at end of file +} diff --git a/tests/yup_audio_basics/yup_MidiFile.cpp b/tests/yup_audio_basics/yup_MidiFile.cpp index 2e8048109..0a5efd9ba 100644 --- a/tests/yup_audio_basics/yup_MidiFile.cpp +++ b/tests/yup_audio_basics/yup_MidiFile.cpp @@ -52,7 +52,7 @@ void writeBytes (OutputStream& os, const std::vector& bytes) } template -Optional parseFile (Fn&& fn) +std::optional parseFile (Fn&& fn) { MemoryOutputStream os; fn (os); @@ -525,7 +525,7 @@ TEST_F (MidiFileTest, ReadTrackRespectsRunningStatus) writeBytes (os, { 0, 0xff, 0x2f, 0 }); }); - ASSERT_TRUE (file.hasValue()); + ASSERT_TRUE (file.has_value()); EXPECT_EQ (file->getNumTracks(), 1); auto* track = file->getTrack (0); @@ -543,7 +543,7 @@ TEST_F (MidiFileTest, HeaderParsingWorks) { // Empty input const auto file = parseFile ([] (OutputStream&) {}); - EXPECT_FALSE (file.hasValue()); + EXPECT_FALSE (file.has_value()); } { @@ -552,7 +552,7 @@ TEST_F (MidiFileTest, HeaderParsingWorks) { writeBytes (os, { 0xff }); }); - EXPECT_FALSE (file.hasValue()); + EXPECT_FALSE (file.has_value()); } { @@ -563,7 +563,7 @@ TEST_F (MidiFileTest, HeaderParsingWorks) writeBytes (os, { 'M', 'T', 'r', 'k', 0, 0, 0, 4, 0, 0xff, 0x2f, 0 }); }); - EXPECT_TRUE (file.hasValue()); + EXPECT_TRUE (file.has_value()); EXPECT_EQ (file->getTimeFormat(), 96); EXPECT_EQ (file->getNumTracks(), 1); } diff --git a/tests/yup_core/yup_Optional.cpp b/tests/yup_core/yup_Optional.cpp deleted file mode 100644 index 047e11ae8..000000000 --- a/tests/yup_core/yup_Optional.cpp +++ /dev/null @@ -1,637 +0,0 @@ -/* - ============================================================================== - - This file is part of the YUP library. - Copyright (c) 2024 - kunitoki@gmail.com - - YUP is an open source library subject to open-source licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - to use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - YUP IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -#include - -#include - -using namespace yup; - -namespace -{ -struct ThrowOnMoveOrSwap -{ - ThrowOnMoveOrSwap() = default; - - ThrowOnMoveOrSwap (ThrowOnMoveOrSwap&&) { throw std::bad_alloc(); } -}; - -void swap (ThrowOnMoveOrSwap&, ThrowOnMoveOrSwap&) { throw std::bad_alloc(); } - -struct ThrowOnCopy -{ - ThrowOnCopy() = default; - - // Put into an invalid state and throw - ThrowOnCopy (const ThrowOnCopy&) - { - value = -100; - throw std::bad_alloc {}; - } - - // Put into an invalid state and throw - ThrowOnCopy& operator= (const ThrowOnCopy&) - { - value = -100; - throw std::bad_alloc {}; - } - - ThrowOnCopy (ThrowOnCopy&&) noexcept = default; - ThrowOnCopy& operator= (ThrowOnCopy&&) noexcept = default; - - ~ThrowOnCopy() = default; - - int value = 0; -}; -} // namespace - -TEST (OptionalTests, DefaultConstructedOptionalIsInvalid) -{ - Optional o; - EXPECT_FALSE (o.hasValue()); -} - -TEST (OptionalTests, ConstructingFromNulloptIsInvalid) -{ - Optional o (nullopt); - EXPECT_FALSE (o.hasValue()); -} - -TEST (OptionalTests, OptionalConstructedFromValueIsValid) -{ - Optional o = 5; - EXPECT_TRUE (o.hasValue()); - EXPECT_EQ (*o, 5); -} - -TEST (OptionalTests, ConstructingFromMovedOptionalCallsAppropriateMemberFunctions) -{ - using Ptr = std::shared_ptr; - auto makePtr = [] - { - return std::make_shared(); - }; - - auto ptr = makePtr(); - Optional original (ptr); - EXPECT_EQ (ptr.use_count(), 2); - - auto other = std::move (original); - EXPECT_TRUE (original.hasValue()); - EXPECT_TRUE (other.hasValue()); - EXPECT_EQ (ptr.use_count(), 2); -} - -TEST (OptionalTests, MovingEmptyOptionalToPopulatedOneDestroysInstance) -{ - using Ptr = std::shared_ptr; - auto makePtr = [] - { - return std::make_shared(); - }; - - auto ptr = makePtr(); - Optional original (ptr); - EXPECT_EQ (ptr.use_count(), 2); - - original = Optional(); - EXPECT_EQ (ptr.use_count(), 1); -} - -TEST (OptionalTests, CopyingEmptyOptionalToPopulatedOneDestroysInstance) -{ - using Ptr = std::shared_ptr; - auto makePtr = [] - { - return std::make_shared(); - }; - - auto ptr = makePtr(); - Optional original (ptr); - EXPECT_EQ (ptr.use_count(), 2); - - Optional empty; - original = empty; - EXPECT_EQ (ptr.use_count(), 1); -} - -TEST (OptionalTests, MovingPopulatedOptionalCallsAppropriateMemberFunctions) -{ - using Ptr = std::shared_ptr; - auto makePtr = [] - { - return std::make_shared(); - }; - - auto a = makePtr(); - auto b = makePtr(); - - Optional aOpt (a); - Optional bOpt (b); - - EXPECT_EQ (a.use_count(), 2); - EXPECT_EQ (b.use_count(), 2); - - aOpt = std::move (bOpt); - - EXPECT_TRUE (aOpt.hasValue()); - EXPECT_TRUE (bOpt.hasValue()); - EXPECT_EQ (a.use_count(), 1); - EXPECT_EQ (b.use_count(), 2); -} - -TEST (OptionalTests, CopyingPopulatedOptionalCallsAppropriateMemberFunctions) -{ - using Ptr = std::shared_ptr; - auto makePtr = [] - { - return std::make_shared(); - }; - - auto a = makePtr(); - auto b = makePtr(); - - Optional aOpt (a); - Optional bOpt (b); - - EXPECT_EQ (a.use_count(), 2); - EXPECT_EQ (b.use_count(), 2); - - aOpt = bOpt; - - EXPECT_TRUE (aOpt.hasValue()); - EXPECT_TRUE (bOpt.hasValue()); - EXPECT_EQ (a.use_count(), 1); - EXPECT_EQ (b.use_count(), 3); -} - -TEST (OptionalTests, StrongExceptionSafetyWhenForwardingOverEmptyObject) -{ - bool threw = false; - Optional a; - - try - { - ThrowOnCopy t; - a = t; - } - catch (const std::bad_alloc&) - { - threw = true; - } - - EXPECT_TRUE (threw); - EXPECT_FALSE (a.hasValue()); -} - -TEST (OptionalTests, WeakExceptionSafetyWhenForwardingOverPopulatedObject) -{ - bool threw = false; - Optional a = ThrowOnCopy(); - a->value = 5; - - try - { - ThrowOnCopy t; - a = t; - } - catch (const std::bad_alloc&) - { - threw = true; - } - - EXPECT_TRUE (threw); - EXPECT_TRUE (a.hasValue()); - EXPECT_EQ (a->value, -100); -} - -TEST (OptionalTests, StrongExceptionSafetyWhenCopyingOverEmptyObject) -{ - bool threw = false; - Optional a; - - try - { - Optional t = ThrowOnCopy {}; - a = t; - } - catch (const std::bad_alloc&) - { - threw = true; - } - - EXPECT_TRUE (threw); - EXPECT_FALSE (a.hasValue()); -} - -TEST (OptionalTests, WeakExceptionSafetyWhenCopyingOverPopulatedObject) -{ - bool threw = false; - Optional a = ThrowOnCopy(); - a->value = 5; - - try - { - Optional t = ThrowOnCopy {}; - a = t; - } - catch (const std::bad_alloc&) - { - threw = true; - } - - EXPECT_TRUE (threw); - EXPECT_TRUE (a.hasValue()); - EXPECT_EQ (a->value, -100); -} - -TEST (OptionalTests, AssigningFromNulloptClearsInstance) -{ - using Ptr = std::shared_ptr; - auto makePtr = [] - { - return std::make_shared(); - }; - - auto ptr = makePtr(); - Optional a (ptr); - EXPECT_EQ (ptr.use_count(), 2); - - a = nullopt; - EXPECT_EQ (ptr.use_count(), 1); -} - -TEST (OptionalTests, CanBeConstructedAndAssignedAndCopiedAndMovedFromCompatibleType) -{ - struct Foo - { - }; - - struct Bar final : public Foo - { - }; - - { - Optional> opt { std::make_shared() }; - opt = std::make_shared(); - } - - { - auto ptr = std::make_shared(); - Optional> bar (ptr); - Optional> foo (bar); - EXPECT_TRUE (ptr.use_count() == 3); - } - - { - auto ptr = std::make_shared(); - Optional> foo (Optional> { ptr }); - EXPECT_TRUE (ptr.use_count() == 2); - } - - { - auto ptr = std::make_shared(); - Optional> bar (ptr); - Optional> foo; - foo = bar; - EXPECT_TRUE (ptr.use_count() == 3); - } - - { - auto ptr = std::make_shared(); - Optional> foo; - foo = Optional> (ptr); - EXPECT_TRUE (ptr.use_count() == 2); - } -} - -TEST (OptionalTests, ExceptionThrownDuringEmplaceLeavesOptionalWithoutValue) -{ - Optional opt { ThrowOnCopy {} }; - bool threw = false; - - try - { - ThrowOnCopy t; - opt.emplace (t); - } - catch (const std::bad_alloc&) - { - threw = true; - } - - EXPECT_TRUE (threw); - EXPECT_TRUE (! opt.hasValue()); -} - -TEST (OptionalTests, SwapDoesNothingToTwoEmptyOptionals) -{ - using Ptr = std::shared_ptr; - - Optional a, b; - EXPECT_TRUE (! a.hasValue()); - EXPECT_TRUE (! b.hasValue()); - - a.swap (b); - - EXPECT_TRUE (! a.hasValue()); - EXPECT_TRUE (! b.hasValue()); -} - -TEST (OptionalTests, SwapTransfersOwnershipIfOneOptionalContainsAValue) -{ - using Ptr = std::shared_ptr; - auto makePtr = [] - { - return std::make_shared(); - }; - - { - Ptr ptr = makePtr(); - Optional a, b = ptr; - EXPECT_FALSE (a.hasValue()); - EXPECT_TRUE (b.hasValue()); - EXPECT_EQ (ptr.use_count(), 2); - - a.swap (b); - - EXPECT_TRUE (a.hasValue()); - EXPECT_FALSE (b.hasValue()); - EXPECT_EQ (ptr.use_count(), 2); - } - - { - auto ptr = makePtr(); - Optional a = ptr, b; - EXPECT_TRUE (a.hasValue()); - EXPECT_FALSE (b.hasValue()); - EXPECT_EQ (ptr.use_count(), 2); - - a.swap (b); - - EXPECT_FALSE (a.hasValue()); - EXPECT_TRUE (b.hasValue()); - EXPECT_EQ (ptr.use_count(), 2); - } -} - -TEST (OptionalTests, SwapCallsStdSwapToSwapTwoPopulatedOptionals) -{ - using Ptr = std::shared_ptr; - auto makePtr = [] - { - return std::make_shared(); - }; - - auto x = makePtr(); - auto y = makePtr(); - - Optional a = x; - Optional b = y; - - EXPECT_TRUE (a.hasValue()); - EXPECT_TRUE (b.hasValue()); - EXPECT_EQ (x.use_count(), 2); - EXPECT_EQ (y.use_count(), 2); - - a.swap (b); - - EXPECT_TRUE (a.hasValue()); - EXPECT_TRUE (b.hasValue()); - EXPECT_EQ (x.use_count(), 2); - EXPECT_EQ (y.use_count(), 2); - EXPECT_EQ (*a, y); - EXPECT_EQ (*b, x); -} - -TEST (OptionalTests, ExceptionThrownDuringSwapLeavesObjectsIntact) -{ - { - Optional a, b; - a.emplace(); - - EXPECT_TRUE (a.hasValue()); - EXPECT_TRUE (! b.hasValue()); - - bool threw = false; - - try - { - a.swap (b); - } - catch (const std::bad_alloc&) - { - threw = true; - } - - EXPECT_TRUE (threw); - EXPECT_TRUE (a.hasValue()); - EXPECT_TRUE (! b.hasValue()); - } - - { - Optional a, b; - b.emplace(); - - EXPECT_TRUE (! a.hasValue()); - EXPECT_TRUE (b.hasValue()); - - bool threw = false; - - try - { - a.swap (b); - } - catch (const std::bad_alloc&) - { - threw = true; - } - - EXPECT_TRUE (threw); - EXPECT_TRUE (! a.hasValue()); - EXPECT_TRUE (b.hasValue()); - } - - { - Optional a, b; - a.emplace(); - b.emplace(); - - EXPECT_TRUE (a.hasValue()); - EXPECT_TRUE (b.hasValue()); - - bool threw = false; - - try - { - a.swap (b); - } - catch (const std::bad_alloc&) - { - threw = true; - } - - EXPECT_TRUE (threw); - EXPECT_TRUE (a.hasValue()); - EXPECT_TRUE (b.hasValue()); - } -} - -TEST (OptionalTests, RelationalTests) -{ - EXPECT_TRUE (Optional (1) == Optional (1)); - EXPECT_TRUE (Optional() == Optional()); - EXPECT_TRUE (! (Optional (1) == Optional())); - EXPECT_TRUE (! (Optional() == Optional (1))); - EXPECT_TRUE (! (Optional (1) == Optional (2))); - - EXPECT_TRUE (Optional (1) != Optional (2)); - EXPECT_TRUE (! (Optional() != Optional())); - EXPECT_TRUE (Optional (1) != Optional()); - EXPECT_TRUE (Optional() != Optional (1)); - EXPECT_TRUE (! (Optional (1) != Optional (1))); - - EXPECT_TRUE (Optional() < Optional (1)); - EXPECT_TRUE (! (Optional (1) < Optional())); - EXPECT_TRUE (! (Optional() < Optional())); - EXPECT_TRUE (Optional (1) < Optional (2)); - - EXPECT_TRUE (Optional() <= Optional (1)); - EXPECT_TRUE (! (Optional (1) <= Optional())); - EXPECT_TRUE (Optional() <= Optional()); - EXPECT_TRUE (Optional (1) <= Optional (2)); - - EXPECT_TRUE (! (Optional() > Optional (1))); - EXPECT_TRUE (Optional (1) > Optional()); - EXPECT_TRUE (! (Optional() > Optional())); - EXPECT_TRUE (! (Optional (1) > Optional (2))); - - EXPECT_TRUE (! (Optional() >= Optional (1))); - EXPECT_TRUE (Optional (1) >= Optional()); - EXPECT_TRUE (Optional() >= Optional()); - EXPECT_TRUE (! (Optional (1) >= Optional (2))); - - EXPECT_TRUE (Optional() == nullopt); - EXPECT_TRUE (! (Optional (1) == nullopt)); - EXPECT_TRUE (nullopt == Optional()); - EXPECT_TRUE (! (nullopt == Optional (1))); - - EXPECT_TRUE (! (Optional() != nullopt)); - EXPECT_TRUE (Optional (1) != nullopt); - EXPECT_TRUE (! (nullopt != Optional())); - EXPECT_TRUE (nullopt != Optional (1)); - - EXPECT_TRUE (! (Optional() < nullopt)); - EXPECT_TRUE (! (Optional (1) < nullopt)); - - EXPECT_TRUE (! (nullopt < Optional())); - EXPECT_TRUE (nullopt < Optional (1)); - - EXPECT_TRUE (Optional() <= nullopt); - EXPECT_TRUE (! (Optional (1) <= nullopt)); - - EXPECT_TRUE (nullopt <= Optional()); - EXPECT_TRUE (nullopt <= Optional (1)); - - EXPECT_TRUE (! (Optional() > nullopt)); - EXPECT_TRUE (Optional (1) > nullopt); - - EXPECT_TRUE (! (nullopt > Optional())); - EXPECT_TRUE (! (nullopt > Optional (1))); - - EXPECT_TRUE (Optional() >= nullopt); - EXPECT_TRUE (Optional (1) >= nullopt); - - EXPECT_TRUE (nullopt >= Optional()); - EXPECT_TRUE (! (nullopt >= Optional (1))); - - EXPECT_TRUE (! (Optional() == 5)); - EXPECT_TRUE (! (Optional (1) == 5)); - EXPECT_TRUE (Optional (1) == 1); - EXPECT_TRUE (! (5 == Optional())); - EXPECT_TRUE (! (5 == Optional (1))); - EXPECT_TRUE (1 == Optional (1)); - - EXPECT_TRUE (Optional() != 5); - EXPECT_TRUE (Optional (1) != 5); - EXPECT_TRUE (! (Optional (1) != 1)); - EXPECT_TRUE (5 != Optional()); - EXPECT_TRUE (5 != Optional (1)); - EXPECT_TRUE (! (1 != Optional (1))); - - EXPECT_TRUE (Optional() < 5); - EXPECT_TRUE (Optional (1) < 5); - EXPECT_TRUE (! (Optional (1) < 1)); - EXPECT_TRUE (! (Optional (1) < 0)); - - EXPECT_TRUE (! (5 < Optional())); - EXPECT_TRUE (! (5 < Optional (1))); - EXPECT_TRUE (! (1 < Optional (1))); - EXPECT_TRUE (0 < Optional (1)); - - EXPECT_TRUE (Optional() <= 5); - EXPECT_TRUE (Optional (1) <= 5); - EXPECT_TRUE (Optional (1) <= 1); - EXPECT_TRUE (! (Optional (1) <= 0)); - - EXPECT_TRUE (! (5 <= Optional())); - EXPECT_TRUE (! (5 <= Optional (1))); - EXPECT_TRUE (1 <= Optional (1)); - EXPECT_TRUE (0 <= Optional (1)); - - EXPECT_TRUE (! (Optional() > 5)); - EXPECT_TRUE (! (Optional (1) > 5)); - EXPECT_TRUE (! (Optional (1) > 1)); - EXPECT_TRUE (Optional (1) > 0); - - EXPECT_TRUE (5 > Optional()); - EXPECT_TRUE (5 > Optional (1)); - EXPECT_TRUE (! (1 > Optional (1))); - EXPECT_TRUE (! (0 > Optional (1))); - - EXPECT_TRUE (! (Optional() >= 5)); - EXPECT_TRUE (! (Optional (1) >= 5)); - EXPECT_TRUE (Optional (1) >= 1); - EXPECT_TRUE (Optional (1) >= 0); - - EXPECT_TRUE (5 >= Optional()); - EXPECT_TRUE (5 >= Optional (1)); - EXPECT_TRUE (1 >= Optional (1)); - EXPECT_TRUE (! (0 >= Optional (1))); -} diff --git a/thirdparty/rive_decoders/rive_decoders.h b/thirdparty/rive_decoders/rive_decoders.h index e84acfaee..d246ff573 100644 --- a/thirdparty/rive_decoders/rive_decoders.h +++ b/thirdparty/rive_decoders/rive_decoders.h @@ -28,7 +28,7 @@ vendor: rive version: 1.0 name: Rive Decoders. - description: The Rive Decoders is a companion library for ratser image decoding. + description: The Rive Decoders is a companion library for raster image decoding backends (libpng, libwebp, libjpeg). website: https://github.com/rive-app/rive-runtime license: MIT From 39a7fb4faf49cfd57fe7b1eb044d4483e14ead59 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Sun, 7 Dec 2025 18:19:56 +0100 Subject: [PATCH 08/30] Improve python packaging --- cmake/yup_python.cmake | 4 +- .../yup_python/scripting/yup_ScriptEngine.cpp | 2 +- python/tools/ArchivePythonStdlib.py | 257 ++++++++++++++++-- 3 files changed, 232 insertions(+), 31 deletions(-) diff --git a/cmake/yup_python.cmake b/cmake/yup_python.cmake index 96822022e..a39133f9e 100644 --- a/cmake/yup_python.cmake +++ b/cmake/yup_python.cmake @@ -26,7 +26,7 @@ function (yup_prepare_python_stdlib target_name python_tools_path output_variabl cmake_parse_arguments (YUP_ARG "${options}" "${one_value_args}" "${multi_value_args}" ${ARGN}) - set (default_ignored_library_patterns "lib2to3" "pydoc_data" "_xxtestfuzz*") + set (default_ignored_library_patterns "lib2to3") set (ignored_library_patterns ${default_ignored_library_patterns}) list (APPEND ignored_library_patterns ${YUP_ARG_IGNORED_LIBRARY_PATTERNS}) @@ -45,7 +45,7 @@ function (yup_prepare_python_stdlib target_name python_tools_path output_variabl get_filename_component (python_root_path "${python_embed_env_SOURCE_DIR}" REALPATH) else() - get_filename_component (python_root_path "${Python_LIBRARY_DIRS}" REALPATH) + get_filename_component (python_root_path "${Python_LIBRARY_DIRS}/.." REALPATH) endif() _yup_message (STATUS "Executing python stdlib archive generator tool") diff --git a/modules/yup_python/scripting/yup_ScriptEngine.cpp b/modules/yup_python/scripting/yup_ScriptEngine.cpp index d86af9bdf..305dc6c05 100644 --- a/modules/yup_python/scripting/yup_ScriptEngine.cpp +++ b/modules/yup_python/scripting/yup_ScriptEngine.cpp @@ -111,7 +111,7 @@ std::unique_ptr ScriptEngine::prepareScriptingHome ( auto mis = MemoryInputStream (mb.getData(), mb.getSize(), false); auto zip = ZipFile (mis); - zip.uncompressTo (libFolder.getParentDirectory()); + zip.uncompressTo (destinationFolder); } //for (auto entry : RangedDirectoryIterator (destinationFolder, true, "*", File::findFiles, File::FollowSymlinks::no)) diff --git a/python/tools/ArchivePythonStdlib.py b/python/tools/ArchivePythonStdlib.py index a45cfe7d5..075c98c5c 100644 --- a/python/tools/ArchivePythonStdlib.py +++ b/python/tools/ArchivePythonStdlib.py @@ -16,6 +16,99 @@ def file_hash(file): return h.hexdigest() +def should_exclude(path, name, exclude_patterns): + """Check if a path should be excluded based on patterns.""" + # Check if the name itself matches any pattern + for pattern in exclude_patterns: + # Exact name match + if name == pattern: + return True + + # Directory patterns (e.g., __pycache__, test) + if pattern.startswith('**/'): + pattern_name = pattern[3:] + if name == pattern_name: + return True + + # Extension patterns (e.g., *.pyc) + if pattern.startswith('**/*.'): + ext = pattern[4:] # Remove **/* + if name.endswith(ext): + return True + + # Wildcard patterns + if '*' in pattern and not pattern.startswith('**/'): + import fnmatch + if fnmatch.fnmatch(name, pattern): + return True + + return False + + +def copy_filtered_tree(src, dst, exclude_patterns, verbose=False): + """Recursively copy directory tree with filtering.""" + if not os.path.exists(dst): + os.makedirs(dst) + + for item in os.listdir(src): + src_path = os.path.join(src, item) + dst_path = os.path.join(dst, item) + + # Check if should exclude + if should_exclude(src_path, item, exclude_patterns): + if verbose: + print(f"Excluding: {os.path.relpath(src_path, src)}") + continue + + if os.path.isdir(src_path): + copy_filtered_tree(src_path, dst_path, exclude_patterns, verbose) + else: + shutil.copy2(src_path, dst_path) + if verbose: + print(f"Copied: {os.path.relpath(src_path, src)}") + + +def clean_duplicate_libraries(directory, verbose=False): + """Keep only one of each dynamic library type.""" + lib_files = {} + + for root, dirs, files in os.walk(directory): + for file in files: + file_path = os.path.join(root, file) + + # Group by base name without version numbers + # e.g., libpython3.13.dylib -> libpython, libpython3.13.a -> libpython + if any(ext in file for ext in ['.dylib', '.dll', '.so', '.a', '.lib']): + # Extract base name and extension type + base_name = file.split('.')[0] + + # Determine library type + if '.dylib' in file: + lib_type = 'dylib' + elif '.dll' in file: + lib_type = 'dll' + elif '.so' in file: + lib_type = 'so' + elif '.a' in file: + lib_type = 'static' + elif '.lib' in file: + lib_type = 'lib' + else: + continue + + key = (base_name, lib_type) + + if key not in lib_files: + lib_files[key] = file_path + if verbose: + print(f"Keeping library: {file}") + else: + # Remove duplicate + if verbose: + print(f"Removing duplicate library: {file}") + os.remove(file_path) + + def make_archive(file, directory, verbose=False): archived_files = [] for dirname, _, files in os.walk(directory): @@ -42,7 +135,7 @@ def make_archive(file, directory, verbose=False): print(f"starting python standard lib archiving tool...") parser = ArgumentParser() - parser.add_argument("-r", "--root-folder", type=Path, help="Path to the python root folder.") + parser.add_argument("-r", "--root-folder", type=Path, help="Path to the python base folder.") parser.add_argument("-o", "--output-folder", type=Path, help="Path to the output folder.") parser.add_argument("-M", "--version-major", type=int, help="Major version number (integer).") parser.add_argument("-m", "--version-minor", type=int, help="Minor version number (integer).") @@ -53,55 +146,163 @@ def make_archive(file, directory, verbose=False): version = f"{args.version_major}.{args.version_minor}" version_nodot = f"{args.version_major}{args.version_minor}" + python_folder_name = f"python{version}" final_location: Path = args.output_folder / "python" - site_packages = final_location / "site-packages" final_archive = args.output_folder / f"python{version_nodot}.zip" temp_archive = args.output_folder / f"temp{version_nodot}.zip" base_python: Path = args.root_folder base_patterns = [ - "**/*.pyc", - "**/__pycache__", - "**/__phello__", - "**/*config-3*", - "**/*tcl*", - "**/*tdbc*", - "**/*tk*", - "**/Tk*", - "**/_tk*", - "**/_test*", - "**/libpython*", - "**/pkgconfig", - "**/idlelib", - "**/site-packages", - "**/test", - "**/turtledemo", - "**/temp_*.txt", - "**/.DS_Store", - "**/EXTERNALLY-MANAGED", - "**/LICENSE.txt", + "*.pyc", + "__pycache__", + "__phello__", + "_tk*", + "_test*", + "_xxtestfuzz*", + "config-3*", + "idlelib", + "pkgconfig", + "pydoc_data", + "test", + "*tcl*", + "*tdbc*", + "*tk*", + "Tk*", + "turtledemo", + ".DS_Store", + "EXTERNALLY-MANAGED", + "LICENSE.txt", ] if args.exclude_patterns: custom_patterns = [x.strip() for x in args.exclude_patterns.replace('"', '').split(";")] base_patterns += custom_patterns - ignored_files = shutil.ignore_patterns(*base_patterns) - print(f"cleaning up {final_location}...") if final_location.exists(): shutil.rmtree(final_location) - print(f"copying library from {base_python} to {final_location}...") - shutil.copytree(base_python, final_location, ignore=ignored_files, dirs_exist_ok=True) - os.makedirs(site_packages, exist_ok=True) + final_location.mkdir(parents=True, exist_ok=True) + # Copy lib folder structure + lib_src = base_python / "lib" + if lib_src.exists(): + lib_dst = final_location / "lib" + lib_dst.mkdir(parents=True, exist_ok=True) + + # Copy python version folder + python_lib_src = lib_src / python_folder_name + if python_lib_src.exists(): + python_lib_dst = lib_dst / python_folder_name + print(f"copying library from {python_lib_src} to {python_lib_dst}...") + copy_filtered_tree(python_lib_src, python_lib_dst, base_patterns, verbose=args.verbose) + + # Create site-packages directory + site_packages = python_lib_dst / "site-packages" + site_packages.mkdir(parents=True, exist_ok=True) + else: + print(f"Warning: Python library path {python_lib_src} does not exist") + + # Copy dynamic libraries from lib root (e.g., libpython3.13.dylib) + print(f"copying dynamic libraries from {lib_src}...") + for item in lib_src.iterdir(): + if item.is_file(): + if any(ext in item.name for ext in ['.dylib', '.dll', '.so', '.a', '.lib']): + if not should_exclude(str(item), item.name, base_patterns): + shutil.copy2(item, lib_dst / item.name) + if args.verbose: + print(f"Copied library: {item.name}") + else: + print(f"Warning: Library path {lib_src} does not exist") + + # Copy bin folder + bin_src = base_python / "bin" + if bin_src.exists(): + bin_dst = final_location / "bin" + bin_dst.mkdir(parents=True, exist_ok=True) + + print(f"copying binaries from {bin_src} to {bin_dst}...") + + # Copy python executables and symlinks (deduplicate with set) + executables = list(set([ + "python3", + "python", + f"python{version}", + f"python{args.version_major}", + f"python{args.version_major}.{args.version_minor}", + ])) + + for executable in executables: + exe_path = bin_src / executable + dst_path = bin_dst / executable + + if exe_path.exists(): + # Skip if destination already exists + if dst_path.exists(): + if args.verbose: + print(f"Skipping existing binary: {executable}") + continue + + if exe_path.is_symlink(): + # Preserve symlinks + link_target = os.readlink(exe_path) + os.symlink(link_target, dst_path) + else: + shutil.copy2(exe_path, dst_path) + if args.verbose: + print(f"Copied binary: {executable}") + else: + print(f"Warning: Binary path {bin_src} does not exist") + + # Copy include folder + include_src = base_python / "include" + if include_src.exists(): + include_dst = final_location / "include" + include_dst.mkdir(parents=True, exist_ok=True) + + print(f"copying include files from {include_src} to {include_dst}...") + + # Copy the python version include folder + python_include_src = include_src / python_folder_name + if python_include_src.exists(): + python_include_dst = include_dst / python_folder_name + shutil.copytree(python_include_src, python_include_dst, dirs_exist_ok=True) + else: + # Try copying the whole include folder + for item in include_src.iterdir(): + if item.is_dir(): + shutil.copytree(item, include_dst / item.name, dirs_exist_ok=True) + else: + shutil.copy2(item, include_dst / item.name) + + if args.verbose: + print(f"Copied include files") + else: + print(f"Warning: Include path {include_src} does not exist") + + # Clean up duplicate libraries + print(f"cleaning up duplicate libraries...") + clean_duplicate_libraries(final_location, verbose=args.verbose) + + # Create archive from final_location contents (not including the python/ wrapper) print(f"making archive {temp_archive} to {final_archive}...") if os.path.exists(final_archive): make_archive(temp_archive, final_location, verbose=args.verbose) if file_hash(temp_archive) != file_hash(final_archive): shutil.copy(temp_archive, final_archive) + os.remove(temp_archive) + print(f"Archive updated") + else: + os.remove(temp_archive) + print(f"Archive unchanged") else: - make_archive(final_archive, final_location) + make_archive(final_archive, final_location, verbose=args.verbose) + print(f"Archive created") + + # Clean up temporary directory + print(f"cleaning up {final_location}...") + shutil.rmtree(final_location) + + print("Done!") From 4846f3dd9f61fb4ee504f1ec9a7ba112e5f95336 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Sun, 7 Dec 2025 18:38:21 +0100 Subject: [PATCH 09/30] Bump pybind11 --- modules/yup_python/pybind11/attr.h | 663 ++- modules/yup_python/pybind11/buffer_info.h | 261 +- modules/yup_python/pybind11/cast.h | 2465 +++++++--- modules/yup_python/pybind11/chrono.h | 191 +- .../yup_python/pybind11/conduit/README.txt | 15 + .../pybind11/conduit/pybind11_conduit_v1.h | 122 + .../conduit/pybind11_platform_abi_id.h | 87 + .../pybind11/conduit/wrap_include_python_h.h | 72 + .../yup_python/pybind11/critical_section.h | 70 + modules/yup_python/pybind11/detail/class.h | 882 ++-- modules/yup_python/pybind11/detail/common.h | 1259 +++-- .../yup_python/pybind11/detail/cpp_conduit.h | 86 + modules/yup_python/pybind11/detail/descr.h | 233 +- .../detail/dynamic_raw_ptr_cast_if_possible.h | 45 + .../pybind11/detail/exception_translation.h | 81 + .../detail/function_record_pyobject.h | 208 + modules/yup_python/pybind11/detail/init.h | 517 +- .../yup_python/pybind11/detail/internals.h | 1172 +++-- .../pybind11/detail/native_enum_data.h | 247 + .../detail/pybind11_namespace_macros.h | 83 + .../pybind11/detail/struct_smart_holder.h | 461 ++ .../pybind11/detail/type_caster_base.h | 1646 +++++-- .../pybind11/detail/using_smart_holder.h | 22 + .../pybind11/detail/value_and_holder.h | 120 + modules/yup_python/pybind11/eigen/matrix.h | 643 ++- modules/yup_python/pybind11/eigen/tensor.h | 467 +- modules/yup_python/pybind11/embed.h | 293 +- modules/yup_python/pybind11/eval.h | 114 +- modules/yup_python/pybind11/functional.h | 186 +- modules/yup_python/pybind11/gil.h | 220 +- .../yup_python/pybind11/gil_safe_call_once.h | 107 + modules/yup_python/pybind11/gil_simple.h | 47 + modules/yup_python/pybind11/native_enum.h | 78 + modules/yup_python/pybind11/numpy.h | 2474 ++++++---- modules/yup_python/pybind11/pybind11.h | 4313 +++++++++++------ modules/yup_python/pybind11/pytypes.h | 2803 +++++++---- modules/yup_python/pybind11/stl.h | 696 ++- modules/yup_python/pybind11/stl/filesystem.h | 128 +- modules/yup_python/pybind11/stl_bind.h | 1039 ++-- modules/yup_python/pybind11/subinterpreter.h | 333 ++ .../pybind11/trampoline_self_life_support.h | 72 + modules/yup_python/pybind11/typing.h | 338 ++ modules/yup_python/pybind11/warnings.h | 85 + 43 files changed, 17581 insertions(+), 7863 deletions(-) create mode 100644 modules/yup_python/pybind11/conduit/README.txt create mode 100644 modules/yup_python/pybind11/conduit/pybind11_conduit_v1.h create mode 100644 modules/yup_python/pybind11/conduit/pybind11_platform_abi_id.h create mode 100644 modules/yup_python/pybind11/conduit/wrap_include_python_h.h create mode 100644 modules/yup_python/pybind11/critical_section.h create mode 100644 modules/yup_python/pybind11/detail/cpp_conduit.h create mode 100644 modules/yup_python/pybind11/detail/dynamic_raw_ptr_cast_if_possible.h create mode 100644 modules/yup_python/pybind11/detail/exception_translation.h create mode 100644 modules/yup_python/pybind11/detail/function_record_pyobject.h create mode 100644 modules/yup_python/pybind11/detail/native_enum_data.h create mode 100644 modules/yup_python/pybind11/detail/pybind11_namespace_macros.h create mode 100644 modules/yup_python/pybind11/detail/struct_smart_holder.h create mode 100644 modules/yup_python/pybind11/detail/using_smart_holder.h create mode 100644 modules/yup_python/pybind11/detail/value_and_holder.h create mode 100644 modules/yup_python/pybind11/gil_safe_call_once.h create mode 100644 modules/yup_python/pybind11/gil_simple.h create mode 100644 modules/yup_python/pybind11/native_enum.h create mode 100644 modules/yup_python/pybind11/subinterpreter.h create mode 100644 modules/yup_python/pybind11/trampoline_self_life_support.h create mode 100644 modules/yup_python/pybind11/typing.h create mode 100644 modules/yup_python/pybind11/warnings.h diff --git a/modules/yup_python/pybind11/attr.h b/modules/yup_python/pybind11/attr.h index 1044db94d..725cada27 100644 --- a/modules/yup_python/pybind11/attr.h +++ b/modules/yup_python/pybind11/attr.h @@ -12,84 +12,134 @@ #include "detail/common.h" #include "cast.h" +#include "trampoline_self_life_support.h" #include -PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) +PYBIND11_NAMESPACE_BEGIN (PYBIND11_NAMESPACE) /// \addtogroup annotations /// @{ /// Annotation for methods -struct is_method { +struct is_method +{ handle class_; - explicit is_method(const handle &c) : class_(c) {} + + explicit is_method (const handle& c) + : class_ (c) + { + } }; /// Annotation for setters -struct is_setter {}; +struct is_setter +{ +}; /// Annotation for operators -struct is_operator {}; +struct is_operator +{ +}; /// Annotation for classes that cannot be subclassed -struct is_final {}; +struct is_final +{ +}; /// Annotation for parent scope -struct scope { +struct scope +{ handle value; - explicit scope(const handle &s) : value(s) {} + + explicit scope (const handle& s) + : value (s) + { + } }; /// Annotation for documentation -struct doc { - const char *value; - explicit doc(const char *value) : value(value) {} +struct doc +{ + const char* value; + + explicit doc (const char* value) + : value (value) + { + } }; /// Annotation for function names -struct name { - const char *value; - explicit name(const char *value) : value(value) {} +struct name +{ + const char* value; + + explicit name (const char* value) + : value (value) + { + } }; /// Annotation indicating that a function is an overload associated with a given "sibling" -struct sibling { +struct sibling +{ handle value; - explicit sibling(const handle &value) : value(value.ptr()) {} + + explicit sibling (const handle& value) + : value (value.ptr()) + { + } }; /// Annotation indicating that a class derives from another given type template -struct base { - - PYBIND11_DEPRECATED( +struct base +{ + PYBIND11_DEPRECATED ( "base() was deprecated in favor of specifying 'T' as a template argument to class_") base() = default; }; /// Keep patient alive while nurse lives template -struct keep_alive {}; +struct keep_alive +{ +}; /// Annotation indicating that a class is involved in a multiple inheritance relationship -struct multiple_inheritance {}; +struct multiple_inheritance +{ +}; /// Annotation which enables dynamic attributes, i.e. adds `__dict__` to a class -struct dynamic_attr {}; +struct dynamic_attr +{ +}; /// Annotation which enables the buffer protocol for a type -struct buffer_protocol {}; +struct buffer_protocol +{ +}; + +/// Annotation which enables releasing the GIL before calling the C++ destructor of wrapped +/// instances (pybind/pybind11#1446). +struct release_gil_before_calling_cpp_dtor +{ +}; /// Annotation which requests that a special metaclass is created for a type -struct metaclass { +struct metaclass +{ handle value; - PYBIND11_DEPRECATED("py::metaclass() is no longer required. It's turned on by default now.") + PYBIND11_DEPRECATED ("py::metaclass() is no longer required. It's turned on by default now.") metaclass() = default; /// Override pybind11's default metaclass - explicit metaclass(handle value) : value(value) {} + explicit metaclass (handle value) + : value (value) + { + } }; /// Specifies a custom callback with signature `void (PyHeapTypeObject*)` that @@ -101,25 +151,38 @@ struct metaclass { /// work with later versions of pybind11. You may wish to consult the /// implementation of `make_new_python_type` in `detail/classes.h` to understand /// the context in which the callback will be run. -struct custom_type_setup { - using callback = std::function; +struct custom_type_setup +{ + using callback = std::function; - explicit custom_type_setup(callback value) : value(std::move(value)) {} + explicit custom_type_setup (callback value) + : value (std::move (value)) + { + } callback value; }; /// Annotation that marks a class as local to the module: -struct module_local { +struct module_local +{ const bool value; - constexpr explicit module_local(bool v = true) : value(v) {} + + constexpr explicit module_local (bool v = true) + : value (v) + { + } }; /// Annotation to mark enums as an arithmetic type -struct arithmetic {}; +struct arithmetic +{ +}; /// Mark a function for addition at the beginning of the existing overload chain instead of the end -struct prepend {}; +struct prepend +{ +}; /** \rst A call policy which places one or more guard variables (``Ts...``) around the function call. @@ -143,77 +206,99 @@ template struct call_guard; template <> -struct call_guard<> { +struct call_guard<> +{ using type = detail::void_type; }; template -struct call_guard { - static_assert(std::is_default_constructible::value, - "The guard type must be default constructible"); +struct call_guard +{ + static_assert (std::is_default_constructible::value, + "The guard type must be default constructible"); using type = T; }; template -struct call_guard { - struct type { - T guard{}; // Compose multiple guard types with left-to-right default-constructor order - typename call_guard::type next{}; +struct call_guard +{ + struct type + { + T guard {}; // Compose multiple guard types with left-to-right default-constructor order + typename call_guard::type next {}; }; }; /// @} annotations -PYBIND11_NAMESPACE_BEGIN(detail) +PYBIND11_NAMESPACE_BEGIN (detail) /* Forward declarations */ enum op_id : int; enum op_type : int; struct undefined_t; template struct op_; -void keep_alive_impl(size_t Nurse, size_t Patient, function_call &call, handle ret); +void keep_alive_impl (size_t Nurse, size_t Patient, function_call& call, handle ret); /// Internal data structure which holds metadata about a keyword argument -struct argument_record { - const char *name; ///< Argument name - const char *descr; ///< Human-readable version of the argument value +struct argument_record +{ + const char* name; ///< Argument name + const char* descr; ///< Human-readable version of the argument value handle value; ///< Associated Python object bool convert : 1; ///< True if the argument is allowed to convert when loading bool none : 1; ///< True if None is allowed when loading - argument_record(const char *name, const char *descr, handle value, bool convert, bool none) - : name(name), descr(descr), value(value), convert(convert), none(none) {} + argument_record (const char* name, const char* descr, handle value, bool convert, bool none) + : name (name) + , descr (descr) + , value (value) + , convert (convert) + , none (none) + { + } }; /// Internal data structure which holds metadata about a bound function (signature, overloads, /// etc.) -struct function_record { +#define PYBIND11_DETAIL_FUNCTION_RECORD_ABI_ID "v1" // PLEASE UPDATE if the struct is changed. + +struct function_record +{ function_record() - : is_constructor(false), is_new_style_constructor(false), is_stateless(false), - is_operator(false), is_method(false), is_setter(false), has_args(false), - has_kwargs(false), prepend(false) {} + : is_constructor (false) + , is_new_style_constructor (false) + , is_stateless (false) + , is_operator (false) + , is_method (false) + , is_setter (false) + , has_args (false) + , has_kwargs (false) + , prepend (false) + { + } /// Function name - char *name = nullptr; /* why no C++ strings? They generate heavier code.. */ + char* name = nullptr; /* why no C++ strings? They generate heavier code.. */ // User-specified documentation string - char *doc = nullptr; + char* doc = nullptr; /// Human-readable version of the function signature - char *signature = nullptr; + char* signature = nullptr; /// List of registered keyword arguments std::vector args; /// Pointer to lambda function which converts arguments and performs the actual call - handle (*impl)(function_call &) = nullptr; + handle (*impl) (function_call&) = nullptr; /// Storage for the wrapped function pointer and captured data, if any - void *data[3] = {}; + void* data[3] = {}; /// Pointer to custom destructor for 'data' (if needed) - void (*free_data)(function_record *ptr) = nullptr; + void (*free_data) (function_record* ptr) = nullptr; /// Return value policy associated with this function return_value_policy policy = return_value_policy::automatic; @@ -256,7 +341,7 @@ struct function_record { std::uint16_t nargs_pos_only = 0; /// Python method object - PyMethodDef *def = nullptr; + PyMethodDef* def = nullptr; /// Python handle to the parent scope (a class or a module) handle scope; @@ -265,23 +350,37 @@ struct function_record { handle sibling; /// Pointer to next overload - function_record *next = nullptr; + function_record* next = nullptr; }; +// The main purpose of this macro is to make it easy to pin-point the critically related code +// sections. +#define PYBIND11_ENSURE_PRECONDITION_FOR_FUNCTIONAL_H_PERFORMANCE_OPTIMIZATIONS(...) \ + static_assert ( \ + __VA_ARGS__, \ + "Violation of precondition for pybind11/functional.h performance optimizations!") + /// Special data structure which (temporarily) holds metadata about a bound class -struct type_record { +struct type_record +{ PYBIND11_NOINLINE type_record() - : multiple_inheritance(false), dynamic_attr(false), buffer_protocol(false), - default_holder(true), module_local(false), is_final(false) {} + : multiple_inheritance (false) + , dynamic_attr (false) + , buffer_protocol (false) + , module_local (false) + , is_final (false) + , release_gil_before_calling_cpp_dtor (false) + { + } /// Handle to the parent scope handle scope; /// Name of the class - const char *name = nullptr; + const char* name = nullptr; // Pointer to RTTI type_info data structure - const std::type_info *type = nullptr; + const std::type_info* type = nullptr; /// How large is the underlying C++ type? size_t type_size = 0; @@ -293,19 +392,27 @@ struct type_record { size_t holder_size = 0; /// The global operator new can be overridden with a class-specific variant - void *(*operator_new)(size_t) = nullptr; + void* (*operator_new) (size_t) = nullptr; /// Function pointer to class_<..>::init_instance - void (*init_instance)(instance *, const void *) = nullptr; + void (*init_instance) (instance*, const void*) = nullptr; /// Function pointer to class_<..>::dealloc - void (*dealloc)(detail::value_and_holder &) = nullptr; + void (*dealloc) (detail::value_and_holder&) = nullptr; + + /// Function pointer for casting alias class (aka trampoline) pointer to + /// trampoline_self_life_support pointer. Sidesteps cross-DSO RTTI issues + /// on platforms like macOS (see PR #5728 for details). + get_trampoline_self_life_support_fn get_trampoline_self_life_support = [] (void*) -> trampoline_self_life_support* + { + return nullptr; + }; /// List of base classes of the newly created type list bases; /// Optional docstring - const char *doc = nullptr; + const char* doc = nullptr; /// Custom metaclass (optional) handle metaclass; @@ -322,54 +429,68 @@ struct type_record { /// Does the class implement the buffer protocol? bool buffer_protocol : 1; - /// Is the default (unique_ptr) holder type used? - bool default_holder : 1; - /// Is the class definition local to the module shared object? bool module_local : 1; /// Is the class inheritable from python classes? bool is_final : 1; - PYBIND11_NOINLINE void add_base(const std::type_info &base, void *(*caster)(void *) ) { - auto *base_info = detail::get_type_info(base, false); - if (!base_info) { - std::string tname(base.name()); - detail::clean_type_id(tname); - pybind11_fail("generic_type: type \"" + std::string(name) - + "\" referenced unknown base type \"" + tname + "\""); + /// Solves pybind/pybind11#1446 + bool release_gil_before_calling_cpp_dtor : 1; + + holder_enum_t holder_enum_v = holder_enum_t::undefined; + + PYBIND11_NOINLINE void add_base (const std::type_info& base, void* (*caster) (void*) ) + { + auto* base_info = detail::get_type_info (base, false); + if (! base_info) + { + std::string tname (base.name()); + detail::clean_type_id (tname); + pybind11_fail ("generic_type: type \"" + std::string (name) + + "\" referenced unknown base type \"" + tname + "\""); } - if (default_holder != base_info->default_holder) { - std::string tname(base.name()); - detail::clean_type_id(tname); - pybind11_fail("generic_type: type \"" + std::string(name) + "\" " - + (default_holder ? "does not have" : "has") - + " a non-default holder type while its base \"" + tname + "\" " - + (base_info->default_holder ? "does not" : "does")); + // SMART_HOLDER_BAKEIN_FOLLOW_ON: Refine holder compatibility checks. + bool this_has_unique_ptr_holder = (holder_enum_v == holder_enum_t::std_unique_ptr); + bool base_has_unique_ptr_holder = (base_info->holder_enum_v == holder_enum_t::std_unique_ptr); + if (this_has_unique_ptr_holder != base_has_unique_ptr_holder) + { + std::string tname (base.name()); + detail::clean_type_id (tname); + pybind11_fail ("generic_type: type \"" + std::string (name) + "\" " + + (this_has_unique_ptr_holder ? "does not have" : "has") + + " a non-default holder type while its base \"" + tname + "\" " + + (base_has_unique_ptr_holder ? "does not" : "does")); } - bases.append((PyObject *) base_info->type); + bases.append ((PyObject*) base_info->type); -#if PY_VERSION_HEX < 0x030B0000 +#ifdef PYBIND11_BACKWARD_COMPATIBILITY_TP_DICTOFFSET dynamic_attr |= base_info->type->tp_dictoffset != 0; #else dynamic_attr |= (base_info->type->tp_flags & Py_TPFLAGS_MANAGED_DICT) != 0; #endif - if (caster) { - base_info->implicit_casts.emplace_back(type, caster); + if (caster) + { + base_info->implicit_casts.emplace_back (type, caster); } } }; -inline function_call::function_call(const function_record &f, handle p) : func(f), parent(p) { - args.reserve(f.nargs); - args_convert.reserve(f.nargs); +inline function_call::function_call (const function_record& f, handle p) + : func (f) + , parent (p) +{ + args.reserve (f.nargs); + args_convert.reserve (f.nargs); } /// Tag for a new-style `__init__` defined in `detail/init.h` -struct is_new_style_constructor {}; +struct is_new_style_constructor +{ +}; /** * Partial template specializations to process custom attributes provided to @@ -381,52 +502,67 @@ template struct process_attribute; template -struct process_attribute_default { +struct process_attribute_default +{ /// Default implementation: do nothing - static void init(const T &, function_record *) {} - static void init(const T &, type_record *) {} - static void precall(function_call &) {} - static void postcall(function_call &, handle) {} + static void init (const T&, function_record*) {} + + static void init (const T&, type_record*) {} + + static void precall (function_call&) {} + + static void postcall (function_call&, handle) {} }; /// Process an attribute specifying the function's name template <> -struct process_attribute : process_attribute_default { - static void init(const name &n, function_record *r) { r->name = const_cast(n.value); } +struct process_attribute : process_attribute_default +{ + static void init (const name& n, function_record* r) { r->name = const_cast (n.value); } }; /// Process an attribute specifying the function's docstring template <> -struct process_attribute : process_attribute_default { - static void init(const doc &n, function_record *r) { r->doc = const_cast(n.value); } +struct process_attribute : process_attribute_default +{ + static void init (const doc& n, function_record* r) { r->doc = const_cast (n.value); } }; /// Process an attribute specifying the function's docstring (provided as a C-style string) template <> -struct process_attribute : process_attribute_default { - static void init(const char *d, function_record *r) { r->doc = const_cast(d); } - static void init(const char *d, type_record *r) { r->doc = d; } +struct process_attribute : process_attribute_default +{ + static void init (const char* d, function_record* r) { r->doc = const_cast (d); } + + static void init (const char* d, type_record* r) { r->doc = d; } }; + template <> -struct process_attribute : process_attribute {}; +struct process_attribute : process_attribute +{ +}; /// Process an attribute indicating the function's return value policy template <> -struct process_attribute : process_attribute_default { - static void init(const return_value_policy &p, function_record *r) { r->policy = p; } +struct process_attribute : process_attribute_default +{ + static void init (const return_value_policy& p, function_record* r) { r->policy = p; } }; /// Process an attribute which indicates that this is an overloaded function associated with a /// given sibling template <> -struct process_attribute : process_attribute_default { - static void init(const sibling &s, function_record *r) { r->sibling = s.value; } +struct process_attribute : process_attribute_default +{ + static void init (const sibling& s, function_record* r) { r->sibling = s.value; } }; /// Process an attribute which indicates that this function is a method template <> -struct process_attribute : process_attribute_default { - static void init(const is_method &s, function_record *r) { +struct process_attribute : process_attribute_default +{ + static void init (const is_method& s, function_record* r) + { r->is_method = true; r->scope = s.class_; } @@ -434,116 +570,144 @@ struct process_attribute : process_attribute_default { /// Process an attribute which indicates that this function is a setter template <> -struct process_attribute : process_attribute_default { - static void init(const is_setter &, function_record *r) { r->is_setter = true; } +struct process_attribute : process_attribute_default +{ + static void init (const is_setter&, function_record* r) { r->is_setter = true; } }; /// Process an attribute which indicates the parent scope of a method template <> -struct process_attribute : process_attribute_default { - static void init(const scope &s, function_record *r) { r->scope = s.value; } +struct process_attribute : process_attribute_default +{ + static void init (const scope& s, function_record* r) { r->scope = s.value; } }; /// Process an attribute which indicates that this function is an operator template <> -struct process_attribute : process_attribute_default { - static void init(const is_operator &, function_record *r) { r->is_operator = true; } +struct process_attribute : process_attribute_default +{ + static void init (const is_operator&, function_record* r) { r->is_operator = true; } }; template <> struct process_attribute - : process_attribute_default { - static void init(const is_new_style_constructor &, function_record *r) { + : process_attribute_default +{ + static void init (const is_new_style_constructor&, function_record* r) + { r->is_new_style_constructor = true; } }; -inline void check_kw_only_arg(const arg &a, function_record *r) { - if (r->args.size() > r->nargs_pos && (!a.name || a.name[0] == '\0')) { - pybind11_fail("arg(): cannot specify an unnamed argument after a kw_only() annotation or " - "args() argument"); +inline void check_kw_only_arg (const arg& a, function_record* r) +{ + if (r->args.size() > r->nargs_pos && (! a.name || a.name[0] == '\0')) + { + pybind11_fail ("arg(): cannot specify an unnamed argument after a kw_only() annotation or " + "args() argument"); } } -inline void append_self_arg_if_needed(function_record *r) { - if (r->is_method && r->args.empty()) { - r->args.emplace_back("self", nullptr, handle(), /*convert=*/true, /*none=*/false); +inline void append_self_arg_if_needed (function_record* r) +{ + if (r->is_method && r->args.empty()) + { + r->args.emplace_back ("self", nullptr, handle(), /*convert=*/true, /*none=*/false); } } /// Process a keyword argument attribute (*without* a default value) template <> -struct process_attribute : process_attribute_default { - static void init(const arg &a, function_record *r) { - append_self_arg_if_needed(r); - r->args.emplace_back(a.name, nullptr, handle(), !a.flag_noconvert, a.flag_none); - - check_kw_only_arg(a, r); +struct process_attribute : process_attribute_default +{ + static void init (const arg& a, function_record* r) + { + append_self_arg_if_needed (r); + r->args.emplace_back (a.name, nullptr, handle(), ! a.flag_noconvert, a.flag_none); + + check_kw_only_arg (a, r); } }; /// Process a keyword argument attribute (*with* a default value) template <> -struct process_attribute : process_attribute_default { - static void init(const arg_v &a, function_record *r) { - if (r->is_method && r->args.empty()) { - r->args.emplace_back( +struct process_attribute : process_attribute_default +{ + static void init (const arg_v& a, function_record* r) + { + if (r->is_method && r->args.empty()) + { + r->args.emplace_back ( "self", /*descr=*/nullptr, /*parent=*/handle(), /*convert=*/true, /*none=*/false); } - if (!a.value) { + if (! a.value) + { #if defined(PYBIND11_DETAILED_ERROR_MESSAGES) - std::string descr("'"); - if (a.name) { - descr += std::string(a.name) + ": "; + std::string descr ("'"); + if (a.name) + { + descr += std::string (a.name) + ": "; } descr += a.type + "'"; - if (r->is_method) { - if (r->name) { - descr += " in method '" + (std::string) str(r->scope) + "." - + (std::string) r->name + "'"; - } else { - descr += " in method of '" + (std::string) str(r->scope) + "'"; + if (r->is_method) + { + if (r->name) + { + descr += " in method '" + (std::string) str (r->scope) + "." + + (std::string) r->name + "'"; + } + else + { + descr += " in method of '" + (std::string) str (r->scope) + "'"; } - } else if (r->name) { + } + else if (r->name) + { descr += " in function '" + (std::string) r->name + "'"; } - pybind11_fail("arg(): could not convert default argument " + descr - + " into a Python object (type not registered yet?)"); + pybind11_fail ("arg(): could not convert default argument " + descr + + " into a Python object (type not registered yet?)"); #else - pybind11_fail("arg(): could not convert default argument " - "into a Python object (type not registered yet?). " - "#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for " - "more information."); + pybind11_fail ("arg(): could not convert default argument " + "into a Python object (type not registered yet?). " + "#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for " + "more information."); #endif } - r->args.emplace_back(a.name, a.descr, a.value.inc_ref(), !a.flag_noconvert, a.flag_none); + r->args.emplace_back (a.name, a.descr, a.value.inc_ref(), ! a.flag_noconvert, a.flag_none); - check_kw_only_arg(a, r); + check_kw_only_arg (a, r); } }; /// Process a keyword-only-arguments-follow pseudo argument template <> -struct process_attribute : process_attribute_default { - static void init(const kw_only &, function_record *r) { - append_self_arg_if_needed(r); - if (r->has_args && r->nargs_pos != static_cast(r->args.size())) { - pybind11_fail("Mismatched args() and kw_only(): they must occur at the same relative " - "argument location (or omit kw_only() entirely)"); +struct process_attribute : process_attribute_default +{ + static void init (const kw_only&, function_record* r) + { + append_self_arg_if_needed (r); + if (r->has_args && r->nargs_pos != static_cast (r->args.size())) + { + pybind11_fail ("Mismatched args() and kw_only(): they must occur at the same relative " + "argument location (or omit kw_only() entirely)"); } - r->nargs_pos = static_cast(r->args.size()); + r->nargs_pos = static_cast (r->args.size()); } }; /// Process a positional-only-argument maker template <> -struct process_attribute : process_attribute_default { - static void init(const pos_only &, function_record *r) { - append_self_arg_if_needed(r); - r->nargs_pos_only = static_cast(r->args.size()); - if (r->nargs_pos_only > r->nargs_pos) { - pybind11_fail("pos_only(): cannot follow a py::args() argument"); +struct process_attribute : process_attribute_default +{ + static void init (const pos_only&, function_record* r) + { + append_self_arg_if_needed (r); + r->nargs_pos_only = static_cast (r->args.size()); + if (r->nargs_pos_only > r->nargs_pos) + { + pybind11_fail ("pos_only(): cannot follow a py::args() argument"); } // It also can't follow a kw_only, but a static_assert in pybind11.h checks that } @@ -553,68 +717,94 @@ struct process_attribute : process_attribute_default { /// that) template struct process_attribute::value>> - : process_attribute_default { - static void init(const handle &h, type_record *r) { r->bases.append(h); } + : process_attribute_default +{ + static void init (const handle& h, type_record* r) { r->bases.append (h); } }; /// Process a parent class attribute (deprecated, does not support multiple inheritance) template -struct process_attribute> : process_attribute_default> { - static void init(const base &, type_record *r) { r->add_base(typeid(T), nullptr); } +struct process_attribute> : process_attribute_default> +{ + static void init (const base&, type_record* r) { r->add_base (typeid (T), nullptr); } }; /// Process a multiple inheritance attribute template <> -struct process_attribute : process_attribute_default { - static void init(const multiple_inheritance &, type_record *r) { +struct process_attribute : process_attribute_default +{ + static void init (const multiple_inheritance&, type_record* r) + { r->multiple_inheritance = true; } }; template <> -struct process_attribute : process_attribute_default { - static void init(const dynamic_attr &, type_record *r) { r->dynamic_attr = true; } +struct process_attribute : process_attribute_default +{ + static void init (const dynamic_attr&, type_record* r) { r->dynamic_attr = true; } }; template <> -struct process_attribute { - static void init(const custom_type_setup &value, type_record *r) { +struct process_attribute +{ + static void init (const custom_type_setup& value, type_record* r) + { r->custom_type_setup_callback = value.value; } }; template <> -struct process_attribute : process_attribute_default { - static void init(const is_final &, type_record *r) { r->is_final = true; } +struct process_attribute : process_attribute_default +{ + static void init (const is_final&, type_record* r) { r->is_final = true; } }; template <> -struct process_attribute : process_attribute_default { - static void init(const buffer_protocol &, type_record *r) { r->buffer_protocol = true; } +struct process_attribute : process_attribute_default +{ + static void init (const buffer_protocol&, type_record* r) { r->buffer_protocol = true; } }; template <> -struct process_attribute : process_attribute_default { - static void init(const metaclass &m, type_record *r) { r->metaclass = m.value; } +struct process_attribute : process_attribute_default +{ + static void init (const metaclass& m, type_record* r) { r->metaclass = m.value; } }; template <> -struct process_attribute : process_attribute_default { - static void init(const module_local &l, type_record *r) { r->module_local = l.value; } +struct process_attribute : process_attribute_default +{ + static void init (const module_local& l, type_record* r) { r->module_local = l.value; } +}; + +template <> +struct process_attribute + : process_attribute_default +{ + static void init (const release_gil_before_calling_cpp_dtor&, type_record* r) + { + r->release_gil_before_calling_cpp_dtor = true; + } }; /// Process a 'prepend' attribute, putting this at the beginning of the overload chain template <> -struct process_attribute : process_attribute_default { - static void init(const prepend &, function_record *r) { r->prepend = true; } +struct process_attribute : process_attribute_default +{ + static void init (const prepend&, function_record* r) { r->prepend = true; } }; /// Process an 'arithmetic' attribute for enums (does nothing here) template <> -struct process_attribute : process_attribute_default {}; +struct process_attribute : process_attribute_default +{ +}; template -struct process_attribute> : process_attribute_default> {}; +struct process_attribute> : process_attribute_default> +{ +}; /** * Process a keep_alive call policy -- invokes keep_alive_impl during the @@ -623,50 +813,70 @@ struct process_attribute> : process_attribute_default struct process_attribute> - : public process_attribute_default> { + : public process_attribute_default> +{ template = 0> - static void precall(function_call &call) { - keep_alive_impl(Nurse, Patient, call, handle()); + static void precall (function_call& call) + { + keep_alive_impl (Nurse, Patient, call, handle()); } + template = 0> - static void postcall(function_call &, handle) {} + static void postcall (function_call&, handle) + { + } + template = 0> - static void precall(function_call &) {} + static void precall (function_call&) + { + } + template = 0> - static void postcall(function_call &call, handle ret) { - keep_alive_impl(Nurse, Patient, call, ret); + static void postcall (function_call& call, handle ret) + { + keep_alive_impl (Nurse, Patient, call, ret); } }; /// Recursively iterate over variadic template arguments template -struct process_attributes { - static void init(const Args &...args, function_record *r) { - PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(r); - PYBIND11_WORKAROUND_INCORRECT_GCC_UNUSED_BUT_SET_PARAMETER(r); +struct process_attributes +{ + static void init (const Args&... args, function_record* r) + { + PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100 (r); + PYBIND11_WORKAROUND_INCORRECT_GCC_UNUSED_BUT_SET_PARAMETER (r); using expander = int[]; - (void) expander{ - 0, ((void) process_attribute::type>::init(args, r), 0)...}; + (void) expander { + 0, ((void) process_attribute::type>::init (args, r), 0)... + }; } - static void init(const Args &...args, type_record *r) { - PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(r); - PYBIND11_WORKAROUND_INCORRECT_GCC_UNUSED_BUT_SET_PARAMETER(r); + + static void init (const Args&... args, type_record* r) + { + PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100 (r); + PYBIND11_WORKAROUND_INCORRECT_GCC_UNUSED_BUT_SET_PARAMETER (r); using expander = int[]; - (void) expander{0, - (process_attribute::type>::init(args, r), 0)...}; + (void) expander { 0, + (process_attribute::type>::init (args, r), 0)... }; } - static void precall(function_call &call) { - PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(call); + + static void precall (function_call& call) + { + PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100 (call); using expander = int[]; - (void) expander{0, - (process_attribute::type>::precall(call), 0)...}; + (void) expander { 0, + (process_attribute::type>::precall (call), 0)... }; } - static void postcall(function_call &call, handle fn_ret) { - PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(call, fn_ret); - PYBIND11_WORKAROUND_INCORRECT_GCC_UNUSED_BUT_SET_PARAMETER(fn_ret); + + static void postcall (function_call& call, handle fn_ret) + { + PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100 (call, fn_ret); + PYBIND11_WORKAROUND_INCORRECT_GCC_UNUSED_BUT_SET_PARAMETER (fn_ret); using expander = int[]; - (void) expander{ - 0, (process_attribute::type>::postcall(call, fn_ret), 0)...}; + (void) expander { + 0, (process_attribute::type>::postcall (call, fn_ret), 0)... + }; } }; @@ -679,12 +889,13 @@ using extract_guard_t = typename exactly_one_t, Extr /// Check the number of named arguments at compile time template ::value...), - size_t self = constexpr_sum(std::is_same::value...)> -constexpr bool expected_num_args(size_t nargs, bool has_args, bool has_kwargs) { - PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(nargs, has_args, has_kwargs); - return named == 0 || (self + named + size_t(has_args) + size_t(has_kwargs)) == nargs; + size_t named = constexpr_sum (std::is_base_of::value...), + size_t self = constexpr_sum (std::is_same::value...)> +constexpr bool expected_num_args (size_t nargs, bool has_args, bool has_kwargs) +{ + PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100 (nargs, has_args, has_kwargs); + return named == 0 || (self + named + size_t (has_args) + size_t (has_kwargs)) == nargs; } -PYBIND11_NAMESPACE_END(detail) -PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) +PYBIND11_NAMESPACE_END (detail) +PYBIND11_NAMESPACE_END (PYBIND11_NAMESPACE) diff --git a/modules/yup_python/pybind11/buffer_info.h b/modules/yup_python/pybind11/buffer_info.h index b99ee8bef..7a48fcc2e 100644 --- a/modules/yup_python/pybind11/buffer_info.h +++ b/modules/yup_python/pybind11/buffer_info.h @@ -11,16 +11,19 @@ #include "detail/common.h" -PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) +PYBIND11_NAMESPACE_BEGIN (PYBIND11_NAMESPACE) -PYBIND11_NAMESPACE_BEGIN(detail) +PYBIND11_NAMESPACE_BEGIN (detail) // Default, C-style strides -inline std::vector c_strides(const std::vector &shape, ssize_t itemsize) { +inline std::vector c_strides (const std::vector& shape, ssize_t itemsize) +{ auto ndim = shape.size(); - std::vector strides(ndim, itemsize); - if (ndim > 0) { - for (size_t i = ndim - 1; i > 0; --i) { + std::vector strides (ndim, itemsize); + if (ndim > 0) + { + for (size_t i = ndim - 1; i > 0; --i) + { strides[i - 1] = strides[i] * shape[i]; } } @@ -28,10 +31,12 @@ inline std::vector c_strides(const std::vector &shape, ssize_t } // F-style strides; default when constructing an array_t with `ExtraFlags & f_style` -inline std::vector f_strides(const std::vector &shape, ssize_t itemsize) { +inline std::vector f_strides (const std::vector& shape, ssize_t itemsize) +{ auto ndim = shape.size(); - std::vector strides(ndim, itemsize); - for (size_t i = 1; i < ndim; ++i) { + std::vector strides (ndim, itemsize); + for (size_t i = 1; i < ndim; ++i) + { strides[i] = strides[i - 1] * shape[i - 1]; } return strides; @@ -40,11 +45,12 @@ inline std::vector f_strides(const std::vector &shape, ssize_t template struct compare_buffer_info; -PYBIND11_NAMESPACE_END(detail) +PYBIND11_NAMESPACE_END (detail) /// Information record describing a Python buffer object -struct buffer_info { - void *ptr = nullptr; // Pointer to the underlying storage +struct buffer_info +{ + void* ptr = nullptr; // Pointer to the underlying storage ssize_t itemsize = 0; // Size of individual items in bytes ssize_t size = 0; // Total number of entries std::string format; // For homogeneous buffers, this should be set to @@ -57,101 +63,127 @@ struct buffer_info { buffer_info() = default; - buffer_info(void *ptr, - ssize_t itemsize, - const std::string &format, - ssize_t ndim, - detail::any_container shape_in, - detail::any_container strides_in, - bool readonly = false) - : ptr(ptr), itemsize(itemsize), size(1), format(format), ndim(ndim), - shape(std::move(shape_in)), strides(std::move(strides_in)), readonly(readonly) { - if (ndim != (ssize_t) shape.size() || ndim != (ssize_t) strides.size()) { - pybind11_fail("buffer_info: ndim doesn't match shape and/or strides length"); + buffer_info (void* ptr, + ssize_t itemsize, + const std::string& format, + ssize_t ndim, + detail::any_container shape_in, + detail::any_container strides_in, + bool readonly = false) + : ptr (ptr) + , itemsize (itemsize) + , size (1) + , format (format) + , ndim (ndim) + , shape (std::move (shape_in)) + , strides (std::move (strides_in)) + , readonly (readonly) + { + if (ndim != (ssize_t) shape.size() || ndim != (ssize_t) strides.size()) + { + pybind11_fail ("buffer_info: ndim doesn't match shape and/or strides length"); } - for (size_t i = 0; i < (size_t) ndim; ++i) { + for (size_t i = 0; i < (size_t) ndim; ++i) + { size *= shape[i]; } } template - buffer_info(T *ptr, - detail::any_container shape_in, - detail::any_container strides_in, - bool readonly = false) - : buffer_info(private_ctr_tag(), - ptr, - sizeof(T), - format_descriptor::format(), - static_cast(shape_in->size()), - std::move(shape_in), - std::move(strides_in), - readonly) {} - - buffer_info(void *ptr, - ssize_t itemsize, - const std::string &format, - ssize_t size, - bool readonly = false) - : buffer_info(ptr, itemsize, format, 1, {size}, {itemsize}, readonly) {} + buffer_info (T* ptr, + detail::any_container shape_in, + detail::any_container strides_in, + bool readonly = false) + : buffer_info (private_ctr_tag(), + ptr, + sizeof (T), + format_descriptor::format(), + static_cast (shape_in->size()), + std::move (shape_in), + std::move (strides_in), + readonly) + { + } + + buffer_info (void* ptr, + ssize_t itemsize, + const std::string& format, + ssize_t size, + bool readonly = false) + : buffer_info (ptr, itemsize, format, 1, { size }, { itemsize }, readonly) + { + } template - buffer_info(T *ptr, ssize_t size, bool readonly = false) - : buffer_info(ptr, sizeof(T), format_descriptor::format(), size, readonly) {} + buffer_info (T* ptr, ssize_t size, bool readonly = false) + : buffer_info (ptr, sizeof (T), format_descriptor::format(), size, readonly) + { + } template - buffer_info(const T *ptr, ssize_t size, bool readonly = true) - : buffer_info( - const_cast(ptr), sizeof(T), format_descriptor::format(), size, readonly) {} - - explicit buffer_info(Py_buffer *view, bool ownview = true) - : buffer_info( - view->buf, - view->itemsize, - view->format, - view->ndim, - {view->shape, view->shape + view->ndim}, - /* Though buffer::request() requests PyBUF_STRIDES, ctypes objects - * ignore this flag and return a view with NULL strides. - * When strides are NULL, build them manually. */ - view->strides - ? std::vector(view->strides, view->strides + view->ndim) - : detail::c_strides({view->shape, view->shape + view->ndim}, view->itemsize), - (view->readonly != 0)) { + buffer_info (const T* ptr, ssize_t size, bool readonly = true) + : buffer_info ( + const_cast (ptr), + sizeof (T), + format_descriptor::format(), + size, + readonly) + { + } + + explicit buffer_info (Py_buffer* view, bool ownview = true) + : buffer_info ( + view->buf, + view->itemsize, + view->format, + view->ndim, + { view->shape, view->shape + view->ndim }, + /* Though buffer::request() requests PyBUF_STRIDES, ctypes objects + * ignore this flag and return a view with NULL strides. + * When strides are NULL, build them manually. */ + view->strides + ? std::vector (view->strides, view->strides + view->ndim) + : detail::c_strides ({ view->shape, view->shape + view->ndim }, view->itemsize), + (view->readonly != 0)) + { // NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer) this->m_view = view; // NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer) this->ownview = ownview; } - buffer_info(const buffer_info &) = delete; - buffer_info &operator=(const buffer_info &) = delete; + buffer_info (const buffer_info&) = delete; + buffer_info& operator= (const buffer_info&) = delete; - buffer_info(buffer_info &&other) noexcept { (*this) = std::move(other); } + buffer_info (buffer_info&& other) noexcept { (*this) = std::move (other); } - buffer_info &operator=(buffer_info &&rhs) noexcept { + buffer_info& operator= (buffer_info&& rhs) noexcept + { ptr = rhs.ptr; itemsize = rhs.itemsize; size = rhs.size; - format = std::move(rhs.format); + format = std::move (rhs.format); ndim = rhs.ndim; - shape = std::move(rhs.shape); - strides = std::move(rhs.strides); - std::swap(m_view, rhs.m_view); - std::swap(ownview, rhs.ownview); + shape = std::move (rhs.shape); + strides = std::move (rhs.strides); + std::swap (m_view, rhs.m_view); + std::swap (ownview, rhs.ownview); readonly = rhs.readonly; return *this; } - ~buffer_info() { - if (m_view && ownview) { - PyBuffer_Release(m_view); + ~buffer_info() + { + if (m_view && ownview) + { + PyBuffer_Release (m_view); delete m_view; } } - Py_buffer *view() const { return m_view; } - Py_buffer *&view() { return m_view; } + Py_buffer* view() const { return m_view; } + + Py_buffer*& view() { return m_view; } /* True if the buffer item type is equivalent to `T`. */ // To define "equivalent" by example: @@ -160,49 +192,64 @@ struct buffer_info { // on some platforms, but `int` and `unsigned` will never be equivalent. // For the ground truth, please inspect `detail::compare_buffer_info<>`. template - bool item_type_is_equivalent_to() const { - return detail::compare_buffer_info::compare(*this); + bool item_type_is_equivalent_to() const + { + return detail::compare_buffer_info::compare (*this); } private: - struct private_ctr_tag {}; - - buffer_info(private_ctr_tag, - void *ptr, - ssize_t itemsize, - const std::string &format, - ssize_t ndim, - detail::any_container &&shape_in, - detail::any_container &&strides_in, - bool readonly) - : buffer_info( - ptr, itemsize, format, ndim, std::move(shape_in), std::move(strides_in), readonly) {} - - Py_buffer *m_view = nullptr; + struct private_ctr_tag + { + }; + + buffer_info (private_ctr_tag, + void* ptr, + ssize_t itemsize, + const std::string& format, + ssize_t ndim, + detail::any_container&& shape_in, + detail::any_container&& strides_in, + bool readonly) + : buffer_info ( + ptr, + itemsize, + format, + ndim, + std::move (shape_in), + std::move (strides_in), + readonly) + { + } + + Py_buffer* m_view = nullptr; bool ownview = false; }; -PYBIND11_NAMESPACE_BEGIN(detail) +PYBIND11_NAMESPACE_BEGIN (detail) template -struct compare_buffer_info { - static bool compare(const buffer_info &b) { +struct compare_buffer_info +{ + static bool compare (const buffer_info& b) + { // NOLINTNEXTLINE(bugprone-sizeof-expression) Needed for `PyObject *` - return b.format == format_descriptor::format() && b.itemsize == (ssize_t) sizeof(T); + return b.format == format_descriptor::format() && b.itemsize == (ssize_t) sizeof (T); } }; template -struct compare_buffer_info::value>> { - static bool compare(const buffer_info &b) { - return (size_t) b.itemsize == sizeof(T) - && (b.format == format_descriptor::value - || ((sizeof(T) == sizeof(long)) - && b.format == (std::is_unsigned::value ? "L" : "l")) - || ((sizeof(T) == sizeof(size_t)) - && b.format == (std::is_unsigned::value ? "N" : "n"))); +struct compare_buffer_info::value>> +{ + static bool compare (const buffer_info& b) + { + return (size_t) b.itemsize == sizeof (T) + && (b.format == format_descriptor::value + || ((sizeof (T) == sizeof (long)) + && b.format == (std::is_unsigned::value ? "L" : "l")) + || ((sizeof (T) == sizeof (size_t)) + && b.format == (std::is_unsigned::value ? "N" : "n"))); } }; -PYBIND11_NAMESPACE_END(detail) -PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) +PYBIND11_NAMESPACE_END (detail) +PYBIND11_NAMESPACE_END (PYBIND11_NAMESPACE) diff --git a/modules/yup_python/pybind11/cast.h b/modules/yup_python/pybind11/cast.h index db3934118..9fc847c87 100644 --- a/modules/yup_python/pybind11/cast.h +++ b/modules/yup_python/pybind11/cast.h @@ -12,6 +12,7 @@ #include "detail/common.h" #include "detail/descr.h" +#include "detail/native_enum_data.h" #include "detail/type_caster_base.h" #include "detail/typeid.h" #include "pytypes.h" @@ -28,86 +29,224 @@ #include #include -PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) +PYBIND11_NAMESPACE_BEGIN (PYBIND11_NAMESPACE) -PYBIND11_WARNING_DISABLE_MSVC(4127) +PYBIND11_WARNING_DISABLE_MSVC (4127) -PYBIND11_NAMESPACE_BEGIN(detail) +PYBIND11_NAMESPACE_BEGIN (detail) template -class type_caster : public type_caster_base {}; +class type_caster : public type_caster_base +{ +}; + template using make_caster = type_caster>; // Shortcut for calling a caster's `cast_op_type` cast operator for casting a type_caster to a T template -typename make_caster::template cast_op_type cast_op(make_caster &caster) { - return caster.operator typename make_caster::template cast_op_type(); +typename make_caster::template cast_op_type cast_op (make_caster& caster) +{ + using result_t = typename make_caster::template cast_op_type; // See PR #4893 + return caster.operator result_t(); } + template typename make_caster::template cast_op_type::type> -cast_op(make_caster &&caster) { - return std::move(caster).operator typename make_caster:: - template cast_op_type::type>(); + cast_op (make_caster&& caster) +{ + using result_t = typename make_caster::template cast_op_type< + typename std::add_rvalue_reference::type>; // See PR #4893 + return std::move (caster).operator result_t(); +} + +template +class type_caster_enum_type +{ +private: + using Underlying = typename std::underlying_type::type; + +public: + static constexpr auto name = const_name(); + + template + static handle cast (SrcType&& src, return_value_policy, handle parent) + { + handle native_enum = global_internals_native_enum_type_map_get_item (std::type_index (typeid (EnumType))); + if (native_enum) + { + return native_enum (static_cast (src)).release(); + } + return type_caster_base::cast ( + std::forward (src), + // Fixes https://github.com/pybind/pybind11/pull/3643#issuecomment-1022987818: + return_value_policy::copy, + parent); + } + + template + static handle cast (SrcType* src, return_value_policy policy, handle parent) + { + return cast (*src, policy, parent); + } + + bool load (handle src, bool convert) + { + handle native_enum = global_internals_native_enum_type_map_get_item (std::type_index (typeid (EnumType))); + if (native_enum) + { + if (! isinstance (src, native_enum)) + { + return false; + } + type_caster underlying_caster; + if (! underlying_caster.load (src.attr ("value"), convert)) + { + pybind11_fail ("native_enum internal consistency failure."); + } + value = static_cast (static_cast (underlying_caster)); + return true; + } + if (! pybind11_enum_) + { + pybind11_enum_.reset (new type_caster_base()); + } + return pybind11_enum_->load (src, convert); + } + + template + using cast_op_type = detail::cast_op_type; + + // NOLINTNEXTLINE(google-explicit-constructor) + operator EnumType*() + { + if (! pybind11_enum_) + { + return &value; + } + return pybind11_enum_->operator EnumType*(); + } + + // NOLINTNEXTLINE(google-explicit-constructor) + operator EnumType&() + { + if (! pybind11_enum_) + { + return value; + } + return pybind11_enum_->operator EnumType&(); + } + +private: + std::unique_ptr> pybind11_enum_; + EnumType value; +}; + +template +struct type_caster_enum_type_enabled : std::true_type +{ +}; + +template +struct type_uses_type_caster_enum_type +{ + static constexpr bool value = std::is_enum::value && type_caster_enum_type_enabled::value; +}; + +template +class type_caster::value>> + : public type_caster_enum_type +{ +}; + +template ::value, int> = 0> +bool isinstance_native_enum_impl (handle obj, const std::type_info& tp) +{ + handle native_enum = global_internals_native_enum_type_map_get_item (tp); + if (! native_enum) + { + return false; + } + return isinstance (obj, native_enum); +} + +template ::value, int> = 0> +bool isinstance_native_enum_impl (handle, const std::type_info&) +{ + return false; +} + +template +bool isinstance_native_enum (handle obj, const std::type_info& tp) +{ + return isinstance_native_enum_impl> (obj, tp); } template -class type_caster> { +class type_caster> +{ private: using caster_t = make_caster; caster_t subcaster; - using reference_t = type &; + using reference_t = type&; using subcaster_cast_op_type = typename caster_t::template cast_op_type; - static_assert( - std::is_same::type &, subcaster_cast_op_type>::value + static_assert ( + std::is_same::type&, subcaster_cast_op_type>::value || std::is_same::value, "std::reference_wrapper caster requires T to have a caster with an " "`operator T &()` or `operator const T &()`"); public: - bool load(handle src, bool convert) { return subcaster.load(src, convert); } + bool load (handle src, bool convert) { return subcaster.load (src, convert); } + static constexpr auto name = caster_t::name; + static handle - cast(const std::reference_wrapper &src, return_value_policy policy, handle parent) { + cast (const std::reference_wrapper& src, return_value_policy policy, handle parent) + { // It is definitely wrong to take ownership of this pointer, so mask that rvp if (policy == return_value_policy::take_ownership - || policy == return_value_policy::automatic) { + || policy == return_value_policy::automatic) + { policy = return_value_policy::automatic_reference; } - return caster_t::cast(&src.get(), policy, parent); + return caster_t::cast (&src.get(), policy, parent); } + template using cast_op_type = std::reference_wrapper; - explicit operator std::reference_wrapper() { return cast_op(subcaster); } -}; - -#define PYBIND11_TYPE_CASTER(type, py_name) \ -protected: \ - type value; \ - \ -public: \ - static constexpr auto name = py_name; \ - template >::value, \ - int> \ - = 0> \ - static ::pybind11::handle cast( \ - T_ *src, ::pybind11::return_value_policy policy, ::pybind11::handle parent) { \ - if (!src) \ - return ::pybind11::none().release(); \ - if (policy == ::pybind11::return_value_policy::take_ownership) { \ - auto h = cast(std::move(*src), policy, parent); \ - delete src; \ - return h; \ - } \ - return cast(*src, policy, parent); \ - } \ - operator type *() { return &value; } /* NOLINT(bugprone-macro-parentheses) */ \ - operator type &() { return value; } /* NOLINT(bugprone-macro-parentheses) */ \ - operator type &&() && { return std::move(value); } /* NOLINT(bugprone-macro-parentheses) */ \ - template \ + + explicit operator std::reference_wrapper() { return cast_op (subcaster); } +}; + +#define PYBIND11_TYPE_CASTER(type, py_name) \ +protected: \ + type value; \ + \ +public: \ + static constexpr auto name = py_name; \ + template >::value, \ + int> = 0> \ + static ::pybind11::handle cast ( \ + T_* src, ::pybind11::return_value_policy policy, ::pybind11::handle parent) \ + { \ + if (! src) \ + return ::pybind11::none().release(); \ + if (policy == ::pybind11::return_value_policy::take_ownership) \ + { \ + auto h = cast (std::move (*src), policy, parent); \ + delete src; \ + return h; \ + } \ + return cast (*src, policy, parent); \ + } \ + operator type*() { return &value; } /* NOLINT(bugprone-macro-parentheses) */ \ + operator type&() { return value; } /* NOLINT(bugprone-macro-parentheses) */ \ + operator type&&() && { return std::move (value); } /* NOLINT(bugprone-macro-parentheses) */ \ + template \ using cast_op_type = ::pybind11::detail::movable_cast_op_type template @@ -121,60 +260,84 @@ using is_std_char_type = any_of, /* std::string */ >; template -struct type_caster::value && !is_std_char_type::value>> { - using _py_type_0 = conditional_t; +struct type_caster::value && ! is_std_char_type::value>> +{ + using _py_type_0 = conditional_t; using _py_type_1 = conditional_t::value, _py_type_0, typename std::make_unsigned<_py_type_0>::type>; using py_type = conditional_t::value, double, _py_type_1>; public: - bool load(handle src, bool convert) { + bool load (handle src, bool convert) + { py_type py_value; - if (!src) { + if (! src) + { return false; } -#if !defined(PYPY_VERSION) - auto index_check = [](PyObject *o) { return PyIndex_Check(o); }; +#if ! defined(PYPY_VERSION) + auto index_check = [] (PyObject* o) + { + return PyIndex_Check (o); + }; #else // In PyPy 7.3.3, `PyIndex_Check` is implemented by calling `__index__`, // while CPython only considers the existence of `nb_index`/`__index__`. - auto index_check = [](PyObject *o) { return hasattr(o, "__index__"); }; + auto index_check = [] (PyObject* o) + { + return hasattr (o, "__index__"); + }; #endif - if (std::is_floating_point::value) { - if (convert || PyFloat_Check(src.ptr())) { - py_value = (py_type) PyFloat_AsDouble(src.ptr()); - } else { + if (std::is_floating_point::value) + { + if (convert || PyFloat_Check (src.ptr())) + { + py_value = (py_type) PyFloat_AsDouble (src.ptr()); + } + else + { return false; } - } else if (PyFloat_Check(src.ptr()) - || (!convert && !PYBIND11_LONG_CHECK(src.ptr()) && !index_check(src.ptr()))) { + } + else if (PyFloat_Check (src.ptr()) + || (! convert && ! PYBIND11_LONG_CHECK (src.ptr()) && ! index_check (src.ptr()))) + { return false; - } else { + } + else + { handle src_or_index = src; // PyPy: 7.3.7's 3.8 does not implement PyLong_*'s __index__ calls. -#if PY_VERSION_HEX < 0x03080000 || defined(PYPY_VERSION) +#if defined(PYPY_VERSION) object index; - if (!PYBIND11_LONG_CHECK(src.ptr())) { // So: index_check(src.ptr()) - index = reinterpret_steal(PyNumber_Index(src.ptr())); - if (!index) { + if (! PYBIND11_LONG_CHECK (src.ptr())) + { // So: index_check(src.ptr()) + index = reinterpret_steal (PyNumber_Index (src.ptr())); + if (! index) + { PyErr_Clear(); - if (!convert) + if (! convert) return false; - } else { + } + else + { src_or_index = index; } } #endif - if (std::is_unsigned::value) { - py_value = as_unsigned(src_or_index.ptr()); - } else { // signed integer: - py_value = sizeof(T) <= sizeof(long) - ? (py_type) PyLong_AsLong(src_or_index.ptr()) - : (py_type) PYBIND11_LONG_AS_LONGLONG(src_or_index.ptr()); + if (std::is_unsigned::value) + { + py_value = as_unsigned (src_or_index.ptr()); + } + else + { // signed integer: + py_value = sizeof (T) <= sizeof (long) + ? (py_type) PyLong_AsLong (src_or_index.ptr()) + : (py_type) PYBIND11_LONG_AS_LONGLONG (src_or_index.ptr()); } } @@ -184,15 +347,17 @@ struct type_caster::value && !is_std_char_t // Check to see if the conversion is valid (integers should match exactly) // Signed/unsigned checks happen elsewhere if (py_err - || (std::is_integral::value && sizeof(py_type) != sizeof(T) - && py_value != (py_type) (T) py_value)) { + || (std::is_integral::value && sizeof (py_type) != sizeof (T) + && py_value != (py_type) (T) py_value)) + { PyErr_Clear(); - if (py_err && convert && (PyNumber_Check(src.ptr()) != 0)) { - auto tmp = reinterpret_steal(std::is_floating_point::value - ? PyNumber_Float(src.ptr()) - : PyNumber_Long(src.ptr())); + if (py_err && convert && (PyNumber_Check (src.ptr()) != 0)) + { + auto tmp = reinterpret_steal (std::is_floating_point::value + ? PyNumber_Float (src.ptr()) + : PyNumber_Long (src.ptr())); PyErr_Clear(); - return load(tmp, false); + return load (tmp, false); } return false; } @@ -203,87 +368,111 @@ struct type_caster::value && !is_std_char_t template static typename std::enable_if::value, handle>::type - cast(U src, return_value_policy /* policy */, handle /* parent */) { - return PyFloat_FromDouble((double) src); + cast (U src, return_value_policy /* policy */, handle /* parent */) + { + return PyFloat_FromDouble ((double) src); } template - static typename std::enable_if::value && std::is_signed::value - && (sizeof(U) <= sizeof(long)), + static typename std::enable_if::value && std::is_signed::value + && (sizeof (U) <= sizeof (long)), handle>::type - cast(U src, return_value_policy /* policy */, handle /* parent */) { - return PYBIND11_LONG_FROM_SIGNED((long) src); + cast (U src, return_value_policy /* policy */, handle /* parent */) + { + return PYBIND11_LONG_FROM_SIGNED ((long) src); } template - static typename std::enable_if::value && std::is_unsigned::value - && (sizeof(U) <= sizeof(unsigned long)), + static typename std::enable_if::value && std::is_unsigned::value + && (sizeof (U) <= sizeof (unsigned long)), handle>::type - cast(U src, return_value_policy /* policy */, handle /* parent */) { - return PYBIND11_LONG_FROM_UNSIGNED((unsigned long) src); + cast (U src, return_value_policy /* policy */, handle /* parent */) + { + return PYBIND11_LONG_FROM_UNSIGNED ((unsigned long) src); } template - static typename std::enable_if::value && std::is_signed::value - && (sizeof(U) > sizeof(long)), + static typename std::enable_if::value && std::is_signed::value + && (sizeof (U) > sizeof (long)), handle>::type - cast(U src, return_value_policy /* policy */, handle /* parent */) { - return PyLong_FromLongLong((long long) src); + cast (U src, return_value_policy /* policy */, handle /* parent */) + { + return PyLong_FromLongLong ((long long) src); } template - static typename std::enable_if::value && std::is_unsigned::value - && (sizeof(U) > sizeof(unsigned long)), + static typename std::enable_if::value && std::is_unsigned::value + && (sizeof (U) > sizeof (unsigned long)), handle>::type - cast(U src, return_value_policy /* policy */, handle /* parent */) { - return PyLong_FromUnsignedLongLong((unsigned long long) src); + cast (U src, return_value_policy /* policy */, handle /* parent */) + { + return PyLong_FromUnsignedLongLong ((unsigned long long) src); } - PYBIND11_TYPE_CASTER(T, const_name::value>("int", "float")); + PYBIND11_TYPE_CASTER (T, + io_name::value> ( + "typing.SupportsInt", + "int", + "typing.SupportsFloat", + "float")); }; template -struct void_caster { +struct void_caster +{ public: - bool load(handle src, bool) { - if (src && src.is_none()) { + bool load (handle src, bool) + { + if (src && src.is_none()) + { return true; } return false; } - static handle cast(T, return_value_policy /* policy */, handle /* parent */) { + + static handle cast (T, return_value_policy /* policy */, handle /* parent */) + { return none().release(); } - PYBIND11_TYPE_CASTER(T, const_name("None")); + + PYBIND11_TYPE_CASTER (T, const_name ("None")); }; template <> -class type_caster : public void_caster {}; +class type_caster : public void_caster +{ +}; template <> -class type_caster : public type_caster { +class type_caster : public type_caster +{ public: using type_caster::cast; - bool load(handle h, bool) { - if (!h) { + bool load (handle h, bool) + { + if (! h) + { return false; } - if (h.is_none()) { + if (h.is_none()) + { value = nullptr; return true; } /* Check if this is a capsule */ - if (isinstance(h)) { - value = reinterpret_borrow(h); + if (isinstance (h)) + { + value = reinterpret_borrow (h); return true; } /* Check if this is a C++ type */ - const auto &bases = all_type_info((PyTypeObject *) type::handle_of(h).ptr()); - if (bases.size() == 1) { // Only allowing loading from a single-value type - value = values_and_holders(reinterpret_cast(h.ptr())).begin()->value_ptr(); + const auto& bases = all_type_info ((PyTypeObject*) type::handle_of (h).ptr()); + if (bases.size() == 1) + { // Only allowing loading from a single-value type + value = values_and_holders (reinterpret_cast (h.ptr())).begin()->value_ptr(); return true; } @@ -291,62 +480,80 @@ class type_caster : public type_caster { return false; } - static handle cast(const void *ptr, return_value_policy /* policy */, handle /* parent */) { - if (ptr) { - return capsule(ptr).release(); + static handle cast (const void* ptr, return_value_policy /* policy */, handle /* parent */) + { + if (ptr) + { + return capsule (ptr).release(); } return none().release(); } template - using cast_op_type = void *&; - explicit operator void *&() { return value; } - static constexpr auto name = const_name("capsule"); + using cast_op_type = void*&; + + explicit operator void*&() { return value; } + + static constexpr auto name = const_name (PYBIND11_CAPSULE_TYPE_TYPE_HINT); private: - void *value = nullptr; + void* value = nullptr; }; template <> -class type_caster : public void_caster {}; +class type_caster : public void_caster +{ +}; template <> -class type_caster { +class type_caster +{ public: - bool load(handle src, bool convert) { - if (!src) { + bool load (handle src, bool convert) + { + if (! src) + { return false; } - if (src.ptr() == Py_True) { + if (src.ptr() == Py_True) + { value = true; return true; } - if (src.ptr() == Py_False) { + if (src.ptr() == Py_False) + { value = false; return true; } - if (convert || (std::strcmp("numpy.bool_", Py_TYPE(src.ptr())->tp_name) == 0)) { - // (allow non-implicit conversion for numpy booleans) + if (convert || is_numpy_bool (src)) + { + // (allow non-implicit conversion for numpy booleans), use strncmp + // since NumPy 1.x had an additional trailing underscore. Py_ssize_t res = -1; - if (src.is_none()) { + if (src.is_none()) + { res = 0; // None is implicitly converted to False } #if defined(PYPY_VERSION) // On PyPy, check that "__bool__" attr exists - else if (hasattr(src, PYBIND11_BOOL_ATTR)) { - res = PyObject_IsTrue(src.ptr()); + else if (hasattr (src, PYBIND11_BOOL_ATTR)) + { + res = PyObject_IsTrue (src.ptr()); } #else // Alternate approach for CPython: this does the same as the above, but optimized // using the CPython API so as to avoid an unneeded attribute lookup. - else if (auto *tp_as_number = src.ptr()->ob_type->tp_as_number) { - if (PYBIND11_NB_BOOL(tp_as_number)) { - res = (*PYBIND11_NB_BOOL(tp_as_number))(src.ptr()); + else if (auto* tp_as_number = Py_TYPE (src.ptr())->tp_as_number) + { + if (PYBIND11_NB_BOOL (tp_as_number)) + { + res = (*PYBIND11_NB_BOOL (tp_as_number)) (src.ptr()); } } #endif - if (res == 0 || res == 1) { + if (res == 0 || res == 1) + { value = (res != 0); return true; } @@ -354,115 +561,137 @@ class type_caster { } return false; } - static handle cast(bool src, return_value_policy /* policy */, handle /* parent */) { - return handle(src ? Py_True : Py_False).inc_ref(); + + static handle cast (bool src, return_value_policy /* policy */, handle /* parent */) + { + return handle (src ? Py_True : Py_False).inc_ref(); + } + + PYBIND11_TYPE_CASTER (bool, const_name ("bool")); + +private: + // Test if an object is a NumPy boolean (without fetching the type). + static inline bool is_numpy_bool (handle object) + { + const char* type_name = Py_TYPE (object.ptr())->tp_name; + // Name changed to `numpy.bool` in NumPy 2, `numpy.bool_` is needed for 1.x support + return std::strcmp ("numpy.bool", type_name) == 0 + || std::strcmp ("numpy.bool_", type_name) == 0; } - PYBIND11_TYPE_CASTER(bool, const_name("bool")); }; // Helper class for UTF-{8,16,32} C++ stl strings: template -struct string_caster { +struct string_caster +{ using CharT = typename StringType::value_type; // Simplify life by being able to assume standard char sizes (the standard only guarantees // minimums, but Python requires exact sizes) - static_assert(!std::is_same::value || sizeof(CharT) == 1, - "Unsupported char size != 1"); + static_assert (! std::is_same::value || sizeof (CharT) == 1, + "Unsupported char size != 1"); #if defined(PYBIND11_HAS_U8STRING) - static_assert(!std::is_same::value || sizeof(CharT) == 1, - "Unsupported char8_t size != 1"); + static_assert (! std::is_same::value || sizeof (CharT) == 1, + "Unsupported char8_t size != 1"); #endif - static_assert(!std::is_same::value || sizeof(CharT) == 2, - "Unsupported char16_t size != 2"); - static_assert(!std::is_same::value || sizeof(CharT) == 4, - "Unsupported char32_t size != 4"); + static_assert (! std::is_same::value || sizeof (CharT) == 2, + "Unsupported char16_t size != 2"); + static_assert (! std::is_same::value || sizeof (CharT) == 4, + "Unsupported char32_t size != 4"); // wchar_t can be either 16 bits (Windows) or 32 (everywhere else) - static_assert(!std::is_same::value || sizeof(CharT) == 2 || sizeof(CharT) == 4, - "Unsupported wchar_t size != 2/4"); - static constexpr size_t UTF_N = 8 * sizeof(CharT); + static_assert (! std::is_same::value || sizeof (CharT) == 2 || sizeof (CharT) == 4, + "Unsupported wchar_t size != 2/4"); + static constexpr size_t UTF_N = 8 * sizeof (CharT); - bool load(handle src, bool) { + bool load (handle src, bool) + { handle load_src = src; - if (!src) { + if (! src) + { return false; } - if (!PyUnicode_Check(load_src.ptr())) { - return load_raw(load_src); + if (! PyUnicode_Check (load_src.ptr())) + { + return load_raw (load_src); } // For UTF-8 we avoid the need for a temporary `bytes` object by using // `PyUnicode_AsUTF8AndSize`. - if (UTF_N == 8) { + if (UTF_N == 8) + { Py_ssize_t size = -1; - const auto *buffer - = reinterpret_cast(PyUnicode_AsUTF8AndSize(load_src.ptr(), &size)); - if (!buffer) { + const auto* buffer = reinterpret_cast (PyUnicode_AsUTF8AndSize (load_src.ptr(), &size)); + if (! buffer) + { PyErr_Clear(); return false; } - value = StringType(buffer, static_cast(size)); + value = StringType (buffer, static_cast (size)); return true; } - auto utfNbytes - = reinterpret_steal(PyUnicode_AsEncodedString(load_src.ptr(), - UTF_N == 8 ? "utf-8" - : UTF_N == 16 ? "utf-16" - : "utf-32", - nullptr)); - if (!utfNbytes) { + auto utfNbytes = reinterpret_steal (PyUnicode_AsEncodedString (load_src.ptr(), + UTF_N == 8 ? "utf-8" + : UTF_N == 16 ? "utf-16" + : "utf-32", + nullptr)); + if (! utfNbytes) + { PyErr_Clear(); return false; } - const auto *buffer - = reinterpret_cast(PYBIND11_BYTES_AS_STRING(utfNbytes.ptr())); - size_t length = (size_t) PYBIND11_BYTES_SIZE(utfNbytes.ptr()) / sizeof(CharT); + const auto* buffer = reinterpret_cast (PYBIND11_BYTES_AS_STRING (utfNbytes.ptr())); + size_t length = (size_t) PYBIND11_BYTES_SIZE (utfNbytes.ptr()) / sizeof (CharT); // Skip BOM for UTF-16/32 - if (UTF_N > 8) { + if (UTF_N > 8) + { buffer++; length--; } - value = StringType(buffer, length); + value = StringType (buffer, length); // If we're loading a string_view we need to keep the encoded Python object alive: - if (IsView) { - loader_life_support::add_patient(utfNbytes); + if (IsView) + { + loader_life_support::add_patient (utfNbytes); } return true; } static handle - cast(const StringType &src, return_value_policy /* policy */, handle /* parent */) { - const char *buffer = reinterpret_cast(src.data()); - auto nbytes = ssize_t(src.size() * sizeof(CharT)); - handle s = decode_utfN(buffer, nbytes); - if (!s) { + cast (const StringType& src, return_value_policy /* policy */, handle /* parent */) + { + const char* buffer = reinterpret_cast (src.data()); + auto nbytes = ssize_t (src.size() * sizeof (CharT)); + handle s = decode_utfN (buffer, nbytes); + if (! s) + { throw error_already_set(); } return s; } - PYBIND11_TYPE_CASTER(StringType, const_name(PYBIND11_STRING_NAME)); + PYBIND11_TYPE_CASTER (StringType, const_name (PYBIND11_STRING_NAME)); private: - static handle decode_utfN(const char *buffer, ssize_t nbytes) { -#if !defined(PYPY_VERSION) - return UTF_N == 8 ? PyUnicode_DecodeUTF8(buffer, nbytes, nullptr) - : UTF_N == 16 ? PyUnicode_DecodeUTF16(buffer, nbytes, nullptr, nullptr) - : PyUnicode_DecodeUTF32(buffer, nbytes, nullptr, nullptr); + static handle decode_utfN (const char* buffer, ssize_t nbytes) + { +#if ! defined(PYPY_VERSION) + return UTF_N == 8 ? PyUnicode_DecodeUTF8 (buffer, nbytes, nullptr) + : UTF_N == 16 ? PyUnicode_DecodeUTF16 (buffer, nbytes, nullptr, nullptr) + : PyUnicode_DecodeUTF32 (buffer, nbytes, nullptr, nullptr); #else // PyPy segfaults when on PyUnicode_DecodeUTF16 (and possibly on PyUnicode_DecodeUTF32 as // well), so bypass the whole thing by just passing the encoding as a string value, which // works properly: - return PyUnicode_Decode(buffer, - nbytes, - UTF_N == 8 ? "utf-8" - : UTF_N == 16 ? "utf-16" - : "utf-32", - nullptr); + return PyUnicode_Decode (buffer, + nbytes, + UTF_N == 8 ? "utf-8" + : UTF_N == 16 ? "utf-16" + : "utf-32", + nullptr); #endif } @@ -470,25 +699,30 @@ struct string_caster { // without any encoding/decoding attempt). For other C++ char sizes this is a no-op. // which supports loading a unicode from a str, doesn't take this path. template - bool load_raw(enable_if_t::value, handle> src) { - if (PYBIND11_BYTES_CHECK(src.ptr())) { + bool load_raw (enable_if_t::value, handle> src) + { + if (PYBIND11_BYTES_CHECK (src.ptr())) + { // We were passed raw bytes; accept it into a std::string or char* // without any encoding attempt. - const char *bytes = PYBIND11_BYTES_AS_STRING(src.ptr()); - if (!bytes) { - pybind11_fail("Unexpected PYBIND11_BYTES_AS_STRING() failure."); + const char* bytes = PYBIND11_BYTES_AS_STRING (src.ptr()); + if (! bytes) + { + pybind11_fail ("Unexpected PYBIND11_BYTES_AS_STRING() failure."); } - value = StringType(bytes, (size_t) PYBIND11_BYTES_SIZE(src.ptr())); + value = StringType (bytes, (size_t) PYBIND11_BYTES_SIZE (src.ptr())); return true; } - if (PyByteArray_Check(src.ptr())) { + if (PyByteArray_Check (src.ptr())) + { // We were passed a bytearray; accept it into a std::string or char* // without any encoding attempt. - const char *bytearray = PyByteArray_AsString(src.ptr()); - if (!bytearray) { - pybind11_fail("Unexpected PyByteArray_AsString() failure."); + const char* bytearray = PyByteArray_AsString (src.ptr()); + if (! bytearray) + { + pybind11_fail ("Unexpected PyByteArray_AsString() failure."); } - value = StringType(bytearray, (size_t) PyByteArray_Size(src.ptr())); + value = StringType (bytearray, (size_t) PyByteArray_Size (src.ptr())); return true; } @@ -496,7 +730,8 @@ struct string_caster { } template - bool load_raw(enable_if_t::value, handle>) { + bool load_raw (enable_if_t::value, handle>) + { return false; } }; @@ -504,19 +739,24 @@ struct string_caster { template struct type_caster, enable_if_t::value>> - : string_caster> {}; + : string_caster> +{ +}; #ifdef PYBIND11_HAS_STRING_VIEW template struct type_caster, enable_if_t::value>> - : string_caster, true> {}; + : string_caster, true> +{ +}; #endif // Type caster for C-style strings. We basically use a std::string type caster, but also add the // ability to use None as a nullptr char* (which the string caster doesn't allow). template -struct type_caster::value>> { +struct type_caster::value>> +{ using StringType = std::basic_string; using StringCaster = make_caster; StringCaster str_caster; @@ -524,51 +764,65 @@ struct type_caster::value>> { CharT one_char = 0; public: - bool load(handle src, bool convert) { - if (!src) { + bool load (handle src, bool convert) + { + if (! src) + { return false; } - if (src.is_none()) { + if (src.is_none()) + { // Defer accepting None to other overloads (if we aren't in convert mode): - if (!convert) { + if (! convert) + { return false; } none = true; return true; } - return str_caster.load(src, convert); + return str_caster.load (src, convert); } - static handle cast(const CharT *src, return_value_policy policy, handle parent) { - if (src == nullptr) { + static handle cast (const CharT* src, return_value_policy policy, handle parent) + { + if (src == nullptr) + { return pybind11::none().release(); } - return StringCaster::cast(StringType(src), policy, parent); + return StringCaster::cast (StringType (src), policy, parent); } - static handle cast(CharT src, return_value_policy policy, handle parent) { - if (std::is_same::value) { - handle s = PyUnicode_DecodeLatin1((const char *) &src, 1, nullptr); - if (!s) { + static handle cast (CharT src, return_value_policy policy, handle parent) + { + if (std::is_same::value) + { + handle s = PyUnicode_DecodeLatin1 ((const char*) &src, 1, nullptr); + if (! s) + { throw error_already_set(); } return s; } - return StringCaster::cast(StringType(1, src), policy, parent); + return StringCaster::cast (StringType (1, src), policy, parent); } - explicit operator CharT *() { - return none ? nullptr : const_cast(static_cast(str_caster).c_str()); + explicit operator CharT*() + { + return none ? nullptr : const_cast (static_cast (str_caster).c_str()); } - explicit operator CharT &() { - if (none) { - throw value_error("Cannot convert None to a character"); + + explicit operator CharT&() + { + if (none) + { + throw value_error ("Cannot convert None to a character"); } - auto &value = static_cast(str_caster); + auto& value = static_cast (str_caster); size_t str_len = value.size(); - if (str_len == 0) { - throw value_error("Cannot convert empty string to a character"); + if (str_len == 0) + { + throw value_error ("Cannot convert empty string to a character"); } // If we're in UTF-8 mode, we have two possible failures: one for a unicode character that @@ -576,120 +830,143 @@ struct type_caster::value>> { // figure out how long the first encoded character is in bytes to distinguish between these // two errors. We also allow want to allow unicode characters U+0080 through U+00FF, as // those can fit into a single char value. - if (StringCaster::UTF_N == 8 && str_len > 1 && str_len <= 4) { - auto v0 = static_cast(value[0]); + if (StringCaster::UTF_N == 8 && str_len > 1 && str_len <= 4) + { + auto v0 = static_cast (value[0]); // low bits only: 0-127 // 0b110xxxxx - start of 2-byte sequence // 0b1110xxxx - start of 3-byte sequence // 0b11110xxx - start of 4-byte sequence - size_t char0_bytes = (v0 & 0x80) == 0 ? 1 - : (v0 & 0xE0) == 0xC0 ? 2 - : (v0 & 0xF0) == 0xE0 ? 3 - : 4; + size_t char0_bytes = (v0 & 0x80) == 0 ? 1 + : (v0 & 0xE0) == 0xC0 ? 2 + : (v0 & 0xF0) == 0xE0 ? 3 + : 4; - if (char0_bytes == str_len) { + if (char0_bytes == str_len) + { // If we have a 128-255 value, we can decode it into a single char: - if (char0_bytes == 2 && (v0 & 0xFC) == 0xC0) { // 0x110000xx 0x10xxxxxx - one_char = static_cast(((v0 & 3) << 6) - + (static_cast(value[1]) & 0x3F)); + if (char0_bytes == 2 && (v0 & 0xFC) == 0xC0) + { // 0x110000xx 0x10xxxxxx + one_char = static_cast (((v0 & 3) << 6) + + (static_cast (value[1]) & 0x3F)); return one_char; } // Otherwise we have a single character, but it's > U+00FF - throw value_error("Character code point not in range(0x100)"); + throw value_error ("Character code point not in range(0x100)"); } } // UTF-16 is much easier: we can only have a surrogate pair for values above U+FFFF, thus a // surrogate pair with total length 2 instantly indicates a range error (but not a "your // string was too long" error). - else if (StringCaster::UTF_N == 16 && str_len == 2) { - one_char = static_cast(value[0]); - if (one_char >= 0xD800 && one_char < 0xE000) { - throw value_error("Character code point not in range(0x10000)"); + else if (StringCaster::UTF_N == 16 && str_len == 2) + { + one_char = static_cast (value[0]); + if (one_char >= 0xD800 && one_char < 0xE000) + { + throw value_error ("Character code point not in range(0x10000)"); } } - if (str_len != 1) { - throw value_error("Expected a character, but multi-character string found"); + if (str_len != 1) + { + throw value_error ("Expected a character, but multi-character string found"); } one_char = value[0]; return one_char; } - static constexpr auto name = const_name(PYBIND11_STRING_NAME); + static constexpr auto name = const_name (PYBIND11_STRING_NAME); template using cast_op_type = pybind11::detail::cast_op_type<_T>; }; // Base implementation for std::tuple and std::pair template