Skip to content

Commit

Permalink
Merge branch 'feature/fallbackfunctions' into feature/Unreal_fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
JBenda committed May 2, 2023
2 parents 0ac3a96 + f147bf8 commit c822526
Show file tree
Hide file tree
Showing 15 changed files with 189 additions and 35 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ Many, but not all features of the Ink language are supported (see Glaring Omissi
* Visit and read counts (`visits` and `CNT?` commands).
* `seq` command and all sequence types (stopping, cycle, shuffle)
* Global store that can be shared between runners
* External function binding (no fallback support yet)
* External function binding (define a function with same signature and name, which will be used if no function is bindeded)
* Tunnels and internal functions
* Ink threads (probably incredibly unstable though)
Expand Down
6 changes: 6 additions & 0 deletions inkcpp/functional.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ namespace ink::runtime::internal
stack->push(value{}.set<value_type::int32>(v));
}

void function_base::push_void(basic_eval_stack* stack)
{
stack->push(values::null);
}


void function_base::push_string(basic_eval_stack* stack, const char* dynamic_string)
{
stack->push(value{}.set<value_type::string>(dynamic_string, true));
Expand Down
4 changes: 3 additions & 1 deletion inkcpp/include/functional.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ namespace ink::runtime::internal
template<typename T>
static void push(basic_eval_stack* stack, const T& value);

static void push_void(basic_eval_stack* stack);

// string special push
static void push_string(basic_eval_stack* stack, const char* dynamic_string);

Expand Down Expand Up @@ -88,7 +90,7 @@ namespace ink::runtime::internal

// Ink expects us to push something
// TODO -- Should be a special "void" value
push(stack, 0);
push_void(stack);
}
else if constexpr (is_string<typename traits::return_type>::value)
{
Expand Down
26 changes: 13 additions & 13 deletions inkcpp/runner_impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -722,9 +722,6 @@ namespace ink::runtime::internal
// If we're on a newline
if (_output.ends_with(value_type::newline))
{
// TODO: REMOVE
// return true;

// Unless we are out of content, we are going to try
// to continue a little further. This is to check for
// glue (which means there is potentially more content
Expand Down Expand Up @@ -962,7 +959,16 @@ namespace ink::runtime::internal
} else {
target = read<uint32_t>();
}
start_frame<frame_type::function>(target);
if (!(flag & CommandFlag::FALLBACK_FUNCTION)) {
start_frame<frame_type::function>(target);
} else {
inkAssert(!_eval.is_empty(), "fallback function but no function call before?");
if(_eval.top_value().type() == value_type::ex_fn_not_found) {
_eval.pop();
inkAssert(target != 0, "Exetrnal function was not binded, and no fallback function provided!");
start_frame<frame_type::function>(target);
}
}
}
break;
case Command::TUNNEL_RETURN:
Expand Down Expand Up @@ -1034,18 +1040,11 @@ namespace ink::runtime::internal
// find and execute. will automatically push a valid if applicable
bool success = _functions.call(functionName, &_eval, numArguments, _globals->strings());

// If we failed, we need to at least pretend so our state doesn't get fucked
// If we failed, notify a potential fallback function
if (!success)
{
// pop arguments
for (int i = 0; i < numArguments; i++)
_eval.pop();

// push void
_eval.push(value());
_eval.push(values::ex_fn_not_found);
}

// TODO: Verify something was found?
}
break;

Expand Down Expand Up @@ -1242,6 +1241,7 @@ namespace ink::runtime::internal
inkAssert(false, "Unrecognized command!");
break;
}

}
#ifndef INK_ENABLE_UNREAL
catch (...)
Expand Down
7 changes: 7 additions & 0 deletions inkcpp/value.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ namespace ink::runtime::internal {
func_start, // start of function marker
func_end, // end of function marker
null, // void value, for function returns
ex_fn_not_found, // value for failed external function calls
tunnel_frame, // return from tunnel
function_frame, // return from function
thread_frame, // return from thread
Expand Down Expand Up @@ -376,6 +377,11 @@ namespace ink::runtime::internal {
return *this;
}
template<>
inline constexpr value& value::set<value_type::ex_fn_not_found>() {
_type = value_type::ex_fn_not_found;
return *this;
}
template<>
inline constexpr value& value::set<value_type::newline>() {
_type = value_type::newline;
return *this;
Expand Down Expand Up @@ -447,5 +453,6 @@ namespace ink::runtime::internal {
static constexpr value func_start = value( value_type::func_start );
static constexpr value func_end = value( value_type::func_end );
static constexpr value null = value( value_type::null );
static constexpr value ex_fn_not_found = value(value_type::ex_fn_not_found);
}
}
41 changes: 34 additions & 7 deletions inkcpp_compiler/binary_emitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ namespace ink::compiler::internal
using std::vector;
using std::map;
using std::string;
using std::tuple;

char* strtok_s(char * s, const char * sep, char** context) {
#if defined(_WIN32) || defined(_WIN64)
Expand Down Expand Up @@ -107,8 +106,9 @@ namespace ink::compiler::internal
_current->children.push_back(container);
_current->indexed_children.insert({ index_in_parent, container });

if (!name.empty())
if (!name.empty()) {
_current->named_children.insert({ name, container });
}
}

// Set this as the current pointer
Expand All @@ -127,6 +127,23 @@ namespace ink::compiler::internal
return _containers.pos();
}

int binary_emitter::function_container_arguments(const std::string& name)
{
if(_root == nullptr) { return -1; }
auto fn = _root->named_children.find(name);
if (fn == _root->named_children.end()) { return -1; }

size_t offset = fn->second->offset;
byte_t cmd = _containers.get(offset);
int arity = 0;
while(static_cast<Command>(cmd) == Command::DEFINE_TEMP) {
offset += 6; // command(1) + flag(1) + variable_name_hash(4)
cmd = _containers.get(offset);
++arity;
}
return arity;
}

void binary_emitter::write_raw(Command command, CommandFlag flag, const char* payload, ink::size_t payload_size)
{
_containers.write(command);
Expand All @@ -142,7 +159,8 @@ namespace ink::compiler::internal

// Note the position of this later so we can resolve the paths at the end
size_t param_position = _containers.pos() - sizeof(uint32_t);
_paths.push_back(std::make_tuple(param_position, path, _current, useCountIndex));
bool op = flag & CommandFlag::FALLBACK_FUNCTION;
_paths.push_back(std::make_tuple(param_position, path, op, _current, useCountIndex));
}

void binary_emitter::write_variable(Command command, CommandFlag flag, const std::string& name)
Expand Down Expand Up @@ -274,8 +292,9 @@ namespace ink::compiler::internal
using std::get;
size_t position = get<0>(pair);
const std::string& path = get<1>(pair);
container_data* context = get<2>(pair);
bool useCountIndex = get<3>(pair);
bool optional = get<2>(pair);
container_data* context = get<3>(pair);
bool useCountIndex = get<4>(pair);

// Start at the root
container_data* container = _root;
Expand Down Expand Up @@ -325,7 +344,10 @@ namespace ink::compiler::internal
// Named child
else
{
container = container->named_children[token];
auto itr = container->named_children.find(token);
container = itr == container->named_children.end()
? nullptr
: itr->second;
}

firstParent = false;
Expand All @@ -350,7 +372,12 @@ namespace ink::compiler::internal
else
{
// Otherwise, write container address
_containers.set(position, container->offset);
if (container == nullptr) {
_containers.set(position, 0);
inkAssert(optional, ("Was not able to resolve a not optional path! '" + path + "'").c_str());
} else {
_containers.set(position, container->offset);
}
}
}
}
Expand Down
8 changes: 7 additions & 1 deletion inkcpp_compiler/binary_emitter.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ namespace ink::compiler::internal
// Begin emitter
virtual uint32_t start_container(int index_in_parent, const std::string& name) override;
virtual uint32_t end_container() override;
virtual int function_container_arguments(const std::string& name) override;
virtual void write_raw(Command command, CommandFlag flag = CommandFlag::NO_FLAGS, const char* payload = nullptr, ink::size_t payload_size = 0) override;
virtual void write_path(Command command, CommandFlag flag, const std::string& path, bool useCountIndex = false) override;
virtual void write_variable(Command command, CommandFlag flag, const std::string& name) override;
Expand Down Expand Up @@ -53,6 +54,11 @@ namespace ink::compiler::internal
binary_stream _lists;
binary_stream _containers;

std::vector<std::tuple<size_t, std::string, container_data*, bool>> _paths;
// positon to write address
// path as string
// if path may not exists (used for function fallbackes)
// container data
// use count index?
std::vector<std::tuple<size_t, std::string, bool, container_data*, bool>> _paths;
};
}
17 changes: 17 additions & 0 deletions inkcpp_compiler/binary_stream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,23 @@ namespace ink
memcpy(ptr, data, len);
}

byte_t binary_stream::get(size_t offset) const
{
// Find slab for offset
unsigned int slab_index = offset / DATA_SIZE;
size_t pos = offset % DATA_SIZE;

// Get slab and ptr
byte_t* slab = nullptr;
if (slab_index < _slabs.size())
slab = _slabs[slab_index];
else if (slab_index == _slabs.size())
slab = _currentSlab;

inkAssert(slab != nullptr, "try to access invalid slab in binary stream");
return slab[pos];
}

void binary_stream::reset()
{
// Delete all slabs
Expand Down
3 changes: 3 additions & 0 deletions inkcpp_compiler/binary_stream.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ namespace ink
// reset to 0
void reset();

// read a byte from stream
byte_t get(size_t offset) const;

private:
// Size of a data slab. Whenever
// a slab runs out of data,
Expand Down
6 changes: 6 additions & 0 deletions inkcpp_compiler/emitter.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ namespace ink::compiler::internal
// ends a container
virtual uint32_t end_container() = 0;

// checks if _root contains a container named name to check
// if name is in valid internal function name
// @return number of arguments functions takes (arity)
// @retval -1 if the function was not found
virtual int function_container_arguments(const std::string& name) = 0;

// Writes a command with an optional payload
virtual void write_raw(Command command, CommandFlag flag = CommandFlag::NO_FLAGS, const char* payload = nullptr, ink::size_t payload_size = 0) = 0;

Expand Down
3 changes: 2 additions & 1 deletion inkcpp_compiler/json_compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,8 @@ namespace ink::compiler::internal
// Encode argument count into command flag and write out the hash of the function name
_emitter->write(Command::CALL_EXTERNAL, hash_string(val.c_str()),
static_cast<CommandFlag>(numArgs));
}
_emitter->write_path(Command::FUNCTION, CommandFlag::FALLBACK_FUNCTION, val);
}

// list initialisation
else if (has(command, "list"))
Expand Down
23 changes: 12 additions & 11 deletions inkcpp_test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
add_executable(inkcpp_test catch.hpp Main.cpp
Array.cpp
Pointer.cpp
Stack.cpp
Callstack.cpp
Restorable.cpp
Value.cpp
Globals.cpp
Lists.cpp
Tags.cpp
NewLines.cpp
)
Array.cpp
Pointer.cpp
Stack.cpp
Callstack.cpp
Restorable.cpp
Value.cpp
Globals.cpp
Lists.cpp
Tags.cpp
NewLines.cpp
FallbackFunction.cpp
)

target_link_libraries(inkcpp_test PUBLIC inkcpp inkcpp_compiler inkcpp_shared)
target_include_directories(inkcpp_test PRIVATE ../shared/private/)
Expand Down
63 changes: 63 additions & 0 deletions inkcpp_test/FallbackFunction.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#include "catch.hpp"
#include "../inkcpp_cl/test.h"

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

#include <cmath>

using namespace ink::runtime;

SCENARIO("run a story with external function and fallback function", "[external function]")
{
GIVEN("story with two external functions, one with fallback")
{
inklecate("ink/FallBack.ink", "FallBack.tmp");
ink::compiler::run("FallBack.tmp", "FallBack.bin");
auto ink = story::from_file("FallBack.bin");
runner thread = ink->new_runner();

WHEN("bind both external functions")
{
int cnt_sqrt = 0;
auto fn_sqrt = [&cnt_sqrt](int x)->int{ ++cnt_sqrt; return sqrt(x); };
int cnt_greeting = 0;
auto fn_greeting = [&cnt_greeting]()->const char*{++cnt_greeting; return "Hohooh"; };

thread->bind("sqrt", fn_sqrt);
thread->bind("greeting", fn_greeting);

std::string out;
REQUIRE_NOTHROW(out = thread->getall());
THEN("Both function should be called the correct amount of times")
{
REQUIRE(out == "Hohooh ! A small demonstraion of my power:\n4 * 4 = 16, stunning i would say\n");
REQUIRE(cnt_sqrt == 2);
REQUIRE(cnt_greeting == 1);
}
}
WHEN("only bind function without fallback")
{
int cnt_sqrt = 0;
auto fn_sqrt = [&cnt_sqrt](int x)->int{++cnt_sqrt; return sqrt(x); };

thread ->bind("sqrt", fn_sqrt);

std::string out;
REQUIRE_NOTHROW(out = thread->getall());;
THEN("Sqrt should be falled twice, and uses default greeting")
{
REQUIRE(out == "Hello ! A small demonstraion of my power:\n4 * 4 = 16, stunning i would say\n");
REQUIRE(cnt_sqrt == 2);
}
}
WHEN("bind no function")
{
std::string out;
REQUIRE_THROWS_AS(out = thread->getall(), ink::ink_exception);
}
}
}
13 changes: 13 additions & 0 deletions inkcpp_test/ink/FallBack.ink
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
-> Start

EXTERNAL sqrt(x)

EXTERNAL greeting()

=== function greeting() ===
~ return "Hello"

== Start ==
{greeting()} ! A small demonstraion of my power:
{sqrt(16)} * {sqrt(16)} = 16, stunning i would say
-> DONE
Loading

0 comments on commit c822526

Please sign in to comment.