Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions inkcpp/numeric_operations.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ namespace casting
// int value can cast to float
case value_type::int32: return static_cast<float>(v.get<value_type::int32>());
case value_type::uint32: return static_cast<float>(v.get<value_type::uint32>());
case value_type::boolean: return v.get<value_type::boolean>() ? 1.0f : 0.0f;
default: inkFail("invalid numeric_cast!"); return 0;
}
}
Expand Down
19 changes: 10 additions & 9 deletions inkcpp/operations.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,15 +86,16 @@ template<typename... T>
ink::runtime::internal::value
ink::runtime::internal::value::redefine(const value& oth, T&... env) const
{
if (type() != oth.type() && (type() == value_type::list_flag || type() == value_type::list)
&& (oth.type() == value_type::list_flag || oth.type() == value_type::list)) {
/// @todo could break origin
if (oth.type() == value_type::list) {
return value{}.set<value_type::list>(oth.get<value_type::list>());
} else {
return value{}.set<value_type::list_flag>(oth.get<value_type::list_flag>());
}
if (type() != oth.type()) {

const value vs[] = {*this, oth};
inkAssert(
casting::common_base<2>(vs) != value_type::none,
"try to redefine value of other type with no cast available"
);

// There's a valid conversion, so redefine as input value.
return oth;
}
inkAssert(type() == oth.type(), "try to redefine value of other type");
return redefine<value_type::OP_BEGIN, T...>(oth, {&env...});
}
101 changes: 60 additions & 41 deletions inkcpp/string_operations.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,60 +8,79 @@

/// defines operations allowed on strings.

namespace ink::runtime::internal {
namespace ink::runtime::internal
{

namespace casting {
// define valid castings
// when operate on float and string, the result is a string
template<>
struct cast<value_type::float32, value_type::string>
{ static constexpr value_type value = value_type::string; };
template<>
struct cast<value_type::int32, value_type::string>
{ static constexpr value_type value = value_type::string; };
template<>
struct cast<value_type::uint32, value_type::string>
{ static constexpr value_type value = value_type::string; };
template<>
struct cast<value_type::string, value_type::newline>
{ static constexpr value_type value = value_type::string; };
}

// operation declaration add
namespace casting
{
// define valid castings
// when operate on float and string, the result is a string
template<>
class operation<Command::ADD, value_type::string, void> : public operation_base<string_table> {
public:
using operation_base::operation_base;
void operator()(basic_eval_stack& stack, value* vals);
struct cast<value_type::float32, value_type::string> {
static constexpr value_type value = value_type::string;
};

// operation declaration equality
template<>
class operation<Command::IS_EQUAL, value_type::string, void> : public operation_base<void> {
public:
using operation_base::operation_base;
void operator()(basic_eval_stack& stack, value* vals);
struct cast<value_type::int32, value_type::string> {
static constexpr value_type value = value_type::string;
};

template<>
class operation<Command::NOT_EQUAL, value_type::string, void> : public operation_base<void> {
public:
using operation_base::operation_base;
void operator()(basic_eval_stack& stack, value* vals);
struct cast<value_type::uint32, value_type::string> {
static constexpr value_type value = value_type::string;
};

template<>
class operation<Command::HAS, value_type::string, void> : public operation_base<void> {
public:
using operation_base::operation_base;
void operator()(basic_eval_stack& stack, value* vals);
struct cast<value_type::boolean, value_type::string> {
static constexpr value_type value = value_type::string;
};

template<>
class operation<Command::HASNT, value_type::string, void> : public operation_base<void> {
public:
using operation_base::operation_base;
void operator()(basic_eval_stack& stack, value* vals);
struct cast<value_type::string, value_type::newline> {
static constexpr value_type value = value_type::string;
};
} // namespace casting

// operation declaration add
template<>
class operation<Command::ADD, value_type::string, void> : public operation_base<string_table>
{
public:
using operation_base::operation_base;
void operator()(basic_eval_stack& stack, value* vals);
};

// operation declaration equality
template<>
class operation<Command::IS_EQUAL, value_type::string, void> : public operation_base<void>
{
public:
using operation_base::operation_base;
void operator()(basic_eval_stack& stack, value* vals);
};

template<>
class operation<Command::NOT_EQUAL, value_type::string, void> : public operation_base<void>
{
public:
using operation_base::operation_base;
void operator()(basic_eval_stack& stack, value* vals);
};

template<>
class operation<Command::HAS, value_type::string, void> : public operation_base<void>
{
public:
using operation_base::operation_base;
void operator()(basic_eval_stack& stack, value* vals);
};

template<>
class operation<Command::HASNT, value_type::string, void> : public operation_base<void>
{
public:
using operation_base::operation_base;
void operator()(basic_eval_stack& stack, value* vals);
};

}
} // namespace ink::runtime::internal
6 changes: 6 additions & 0 deletions inkcpp/string_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,18 @@ inline int toStr(char* buffer, size_t size, const char* c)
return 0;
}

inline int toStr(char* buffer, size_t size, bool b)
{
return toStr(buffer, size, b ? "true" : "false");
}

inline int toStr(char* buffer, size_t size, const value& v)
{
switch (v.type()) {
case value_type::int32: return toStr(buffer, size, v.get<value_type::int32>());
case value_type::uint32: return toStr(buffer, size, v.get<value_type::uint32>());
case value_type::float32: return toStr(buffer, size, v.get<value_type::float32>());
case value_type::boolean: return toStr(buffer, size, v.get<value_type::boolean>());
case value_type::newline: return toStr(buffer, size, "\n");
default: inkFail("only support toStr for numeric types"); return -1;
}
Expand Down
32 changes: 32 additions & 0 deletions inkcpp_test/Fixes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,35 @@ SCENARIO("missing leading whitespace inside choice-only text and glued text _ #1
}
}
}

SCENARIO("Casting during redefinition is too strict _ #134", "[fixes]")
{
GIVEN("story with problematic text")
{
auto ink = story::from_file(INK_TEST_RESOURCE_DIR "134_restrictive_casts.bin");
runner thread = ink->new_runner();

WHEN("run story")
{
// Initial casts/assignments are allowed.
auto line = thread->getline();
THEN("expect initial values") { REQUIRE(line == "true 1 1 text A\n"); }
line = thread->getline();
THEN("expect evaluated") { REQUIRE(line == "1.5 1.5 1.5 text0.5 B\n"); }
line = thread->getline();
THEN("expect assigned") { REQUIRE(line == "1.5 1.5 1.5 text0.5 B\n"); }
}

// Six cases that should fail. We can't pollute lookahead with these so they need to be
// separated out.
for (int i = 0; i < 6; ++i) {
WHEN("Jump to failing case")
{
const std::string name = "Fail" + std::to_string(i);
REQUIRE_NOTHROW(thread->move_to(ink::hash_string(name.c_str())));
std::string line;
REQUIRE_THROWS_AS(line = thread->getline(), ink::ink_exception);
}
}
}
}
1 change: 1 addition & 0 deletions inkcpp_test/UTF8.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "catch.hpp"

#include <story.h>
#include <globals.h>
#include <runner.h>
#include <compiler.h>

Expand Down
51 changes: 51 additions & 0 deletions inkcpp_test/ink/134_restrictive_casts.ink
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
VAR b = true
VAR i = 1
VAR f = 1.0
VAR t = "text"
LIST l = (A), B
VAR d = ->Knot

// Input with initial values
{b} {i} {f} {t} {l} {d}

// Cast during evaluation
{b+0.5} {i+0.5} {f+0.5} {t+0.5} {l+1}

// Cast by variable redefinition
~b = b + 0.5
~i = i + 0.5
~f = f + 0.5
~t = t + 0.5
~l = l + 1
{b} {i} {f} {t} {l}
->DONE

===Knot
->DONE

===Fail0
{l + true}
->DONE

===Fail1
{l + 0.5}
->DONE

===Fail2
{d + 0.5}
->DONE

===Fail3
~l = l + true
{l}
->DONE

===Fail4
~l = l + 0.5
{l}
->DONE

===Fail5
~d = d + 0.5
{d}
->DONE
Loading