Skip to content

Commit 7511e5f

Browse files
committed
Fix restrictive numeric casts/assignments. (JBenda#134)
Allow type conversion on redefine if the casting matrix says the types have a common base. Allow bool->float conversion.
1 parent 3f7f6f2 commit 7511e5f

File tree

7 files changed

+161
-50
lines changed

7 files changed

+161
-50
lines changed

inkcpp/numeric_operations.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ namespace casting
116116
// int value can cast to float
117117
case value_type::int32: return static_cast<float>(v.get<value_type::int32>());
118118
case value_type::uint32: return static_cast<float>(v.get<value_type::uint32>());
119+
case value_type::boolean: return v.get<value_type::boolean>() ? 1.0f : 0.0f;
119120
default: inkFail("invalid numeric_cast!"); return 0;
120121
}
121122
}

inkcpp/operations.h

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -86,15 +86,16 @@ template<typename... T>
8686
ink::runtime::internal::value
8787
ink::runtime::internal::value::redefine(const value& oth, T&... env) const
8888
{
89-
if (type() != oth.type() && (type() == value_type::list_flag || type() == value_type::list)
90-
&& (oth.type() == value_type::list_flag || oth.type() == value_type::list)) {
91-
/// @todo could break origin
92-
if (oth.type() == value_type::list) {
93-
return value{}.set<value_type::list>(oth.get<value_type::list>());
94-
} else {
95-
return value{}.set<value_type::list_flag>(oth.get<value_type::list_flag>());
96-
}
89+
if (type() != oth.type()) {
90+
91+
const value vs[] = {*this, oth};
92+
inkAssert(
93+
casting::common_base<2>(vs) != value_type::none,
94+
"try to redefine value of other type with no cast available"
95+
);
96+
97+
// There's a valid conversion, so redefine as input value.
98+
return oth;
9799
}
98-
inkAssert(type() == oth.type(), "try to redefine value of other type");
99100
return redefine<value_type::OP_BEGIN, T...>(oth, {&env...});
100101
}

inkcpp/string_operations.h

Lines changed: 60 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -8,60 +8,79 @@
88

99
/// defines operations allowed on strings.
1010

11-
namespace ink::runtime::internal {
11+
namespace ink::runtime::internal
12+
{
1213

13-
namespace casting {
14-
// define valid castings
15-
// when operate on float and string, the result is a string
16-
template<>
17-
struct cast<value_type::float32, value_type::string>
18-
{ static constexpr value_type value = value_type::string; };
19-
template<>
20-
struct cast<value_type::int32, value_type::string>
21-
{ static constexpr value_type value = value_type::string; };
22-
template<>
23-
struct cast<value_type::uint32, value_type::string>
24-
{ static constexpr value_type value = value_type::string; };
25-
template<>
26-
struct cast<value_type::string, value_type::newline>
27-
{ static constexpr value_type value = value_type::string; };
28-
}
29-
30-
// operation declaration add
14+
namespace casting
15+
{
16+
// define valid castings
17+
// when operate on float and string, the result is a string
3118
template<>
32-
class operation<Command::ADD, value_type::string, void> : public operation_base<string_table> {
33-
public:
34-
using operation_base::operation_base;
35-
void operator()(basic_eval_stack& stack, value* vals);
19+
struct cast<value_type::float32, value_type::string> {
20+
static constexpr value_type value = value_type::string;
3621
};
3722

38-
// operation declaration equality
3923
template<>
40-
class operation<Command::IS_EQUAL, value_type::string, void> : public operation_base<void> {
41-
public:
42-
using operation_base::operation_base;
43-
void operator()(basic_eval_stack& stack, value* vals);
24+
struct cast<value_type::int32, value_type::string> {
25+
static constexpr value_type value = value_type::string;
4426
};
4527

4628
template<>
47-
class operation<Command::NOT_EQUAL, value_type::string, void> : public operation_base<void> {
48-
public:
49-
using operation_base::operation_base;
50-
void operator()(basic_eval_stack& stack, value* vals);
29+
struct cast<value_type::uint32, value_type::string> {
30+
static constexpr value_type value = value_type::string;
5131
};
5232

5333
template<>
54-
class operation<Command::HAS, value_type::string, void> : public operation_base<void> {
55-
public:
56-
using operation_base::operation_base;
57-
void operator()(basic_eval_stack& stack, value* vals);
34+
struct cast<value_type::boolean, value_type::string> {
35+
static constexpr value_type value = value_type::string;
5836
};
5937

6038
template<>
61-
class operation<Command::HASNT, value_type::string, void> : public operation_base<void> {
62-
public:
63-
using operation_base::operation_base;
64-
void operator()(basic_eval_stack& stack, value* vals);
39+
struct cast<value_type::string, value_type::newline> {
40+
static constexpr value_type value = value_type::string;
6541
};
42+
} // namespace casting
43+
44+
// operation declaration add
45+
template<>
46+
class operation<Command::ADD, value_type::string, void> : public operation_base<string_table>
47+
{
48+
public:
49+
using operation_base::operation_base;
50+
void operator()(basic_eval_stack& stack, value* vals);
51+
};
52+
53+
// operation declaration equality
54+
template<>
55+
class operation<Command::IS_EQUAL, value_type::string, void> : public operation_base<void>
56+
{
57+
public:
58+
using operation_base::operation_base;
59+
void operator()(basic_eval_stack& stack, value* vals);
60+
};
61+
62+
template<>
63+
class operation<Command::NOT_EQUAL, value_type::string, void> : public operation_base<void>
64+
{
65+
public:
66+
using operation_base::operation_base;
67+
void operator()(basic_eval_stack& stack, value* vals);
68+
};
69+
70+
template<>
71+
class operation<Command::HAS, value_type::string, void> : public operation_base<void>
72+
{
73+
public:
74+
using operation_base::operation_base;
75+
void operator()(basic_eval_stack& stack, value* vals);
76+
};
77+
78+
template<>
79+
class operation<Command::HASNT, value_type::string, void> : public operation_base<void>
80+
{
81+
public:
82+
using operation_base::operation_base;
83+
void operator()(basic_eval_stack& stack, value* vals);
84+
};
6685

67-
}
86+
} // namespace ink::runtime::internal

inkcpp/string_utils.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,12 +100,18 @@ inline int toStr(char* buffer, size_t size, const char* c)
100100
return 0;
101101
}
102102

103+
inline int toStr(char* buffer, size_t size, bool b)
104+
{
105+
return toStr(buffer, size, b ? "true" : "false");
106+
}
107+
103108
inline int toStr(char* buffer, size_t size, const value& v)
104109
{
105110
switch (v.type()) {
106111
case value_type::int32: return toStr(buffer, size, v.get<value_type::int32>());
107112
case value_type::uint32: return toStr(buffer, size, v.get<value_type::uint32>());
108113
case value_type::float32: return toStr(buffer, size, v.get<value_type::float32>());
114+
case value_type::boolean: return toStr(buffer, size, v.get<value_type::boolean>());
109115
case value_type::newline: return toStr(buffer, size, "\n");
110116
default: inkFail("only support toStr for numeric types"); return -1;
111117
}

inkcpp_test/Fixes.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,3 +126,35 @@ SCENARIO("missing leading whitespace inside choice-only text and glued text _ #1
126126
}
127127
}
128128
}
129+
130+
SCENARIO("Casting during redefinition is too strict _ #134", "[fixes]")
131+
{
132+
GIVEN("story with problematic text")
133+
{
134+
auto ink = story::from_file(INK_TEST_RESOURCE_DIR "134_restrictive_casts.bin");
135+
runner thread = ink->new_runner();
136+
137+
WHEN("run story")
138+
{
139+
// Initial casts/assignments are allowed.
140+
auto line = thread->getline();
141+
THEN("expect initial values") { REQUIRE(line == "true 1 1 text A\n"); }
142+
line = thread->getline();
143+
THEN("expect evaluated") { REQUIRE(line == "1.5 1.5 1.5 text0.5 B\n"); }
144+
line = thread->getline();
145+
THEN("expect assigned") { REQUIRE(line == "1.5 1.5 1.5 text0.5 B\n"); }
146+
}
147+
148+
// Six cases that should fail. We can't pollute lookahead with these so they need to be
149+
// separated out.
150+
for (int i = 0; i < 6; ++i) {
151+
WHEN("Jump to failing case")
152+
{
153+
const std::string name = "Fail" + std::to_string(i);
154+
REQUIRE_NOTHROW(thread->move_to(ink::hash_string(name.c_str())));
155+
std::string line;
156+
REQUIRE_THROWS_AS(line = thread->getline(), ink::ink_exception);
157+
}
158+
}
159+
}
160+
}

inkcpp_test/UTF8.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "catch.hpp"
22

33
#include <story.h>
4+
#include <globals.h>
45
#include <runner.h>
56
#include <compiler.h>
67

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
VAR b = true
2+
VAR i = 1
3+
VAR f = 1.0
4+
VAR t = "text"
5+
LIST l = (A), B
6+
VAR d = ->Knot
7+
8+
// Input with initial values
9+
{b} {i} {f} {t} {l} {d}
10+
11+
// Cast during evaluation
12+
{b+0.5} {i+0.5} {f+0.5} {t+0.5} {l+1}
13+
14+
// Cast by variable redefinition
15+
~b = b + 0.5
16+
~i = i + 0.5
17+
~f = f + 0.5
18+
~t = t + 0.5
19+
~l = l + 1
20+
{b} {i} {f} {t} {l}
21+
->DONE
22+
23+
===Knot
24+
->DONE
25+
26+
===Fail0
27+
{l + true}
28+
->DONE
29+
30+
===Fail1
31+
{l + 0.5}
32+
->DONE
33+
34+
===Fail2
35+
{d + 0.5}
36+
->DONE
37+
38+
===Fail3
39+
~l = l + true
40+
{l}
41+
->DONE
42+
43+
===Fail4
44+
~l = l + 0.5
45+
{l}
46+
->DONE
47+
48+
===Fail5
49+
~d = d + 0.5
50+
{d}
51+
->DONE

0 commit comments

Comments
 (0)