Giter Club home page Giter Club logo

jinx's Introduction

Jinx CI

Jinx is a lightweight embeddable scripting language, written in modern C++, intended to be compiled and used from within a host application. The Jinx language is clean and simple, looking like a cross between pseudo-code and natural language phrases, thanks to its highly flexible function syntax and identifiers that can incorporate whitespace.

It was inspired by languages such as Lua, Python, C++, AppleScript, and a few others. A significant design goal was to make Jinx not only simple to use as a language, but also to make it straightforward to integrate and use via the native API. Jinx scripts are designed to be executed asynchronously as co-routines, or even on separate threads, since the runtime is thread-safe. The language also uses no garbage collection, ensuring predictable CPU usage. This makes it a potential candidate for real-time applications like videogames.

You can visit the main Jinx website here, or see recent changes

Getting Started with Jinx

Jinx requires a C++ 17 compliant compiler to build, and it is available both as a traditional or header-only library.

Jinx supports the CMake build system, so for CMake users, adding Jinx should be as simple as pointing your build scripts to the root folder of the project. Stand-alone build scripts for several platforms/IDEs are found in the Bin folder. Alternatively, to build the library manually, copy all the files within the Source folder to your project, add them via your IDE or build system of choice, and include Source/Jinx.h from your own source files. This should be reasonably simple, since the entire library is contained in a single folder.

Using the header-only version is even simple. Include the header file Include/Jinx.hpp from your own source, which contains the entire library in a single amalgamated header.

Your First Jinx Script

Jinx is incredibly simple to use, requiring only a few lines of C++ code to compile and execute your first script. Here's a Hello, World that not only demonstrates the Jinx script, but all the C++ code required as well.

#include "Jinx.h"
    
// Create the Jinx runtime object
auto runtime = Jinx::CreateRuntime();
    
// Text containing our Jinx script
const char * scriptText =
u8R"(
    
-- Use the core library
import core
    
-- Write to the debug output
write line "Hello, world!"
    
)";
    
// Create and execute a script object
auto script = runtime->ExecuteScript(scriptText);

There is a more comprehensive tutorial on the Jinx website for anyone who wants to dive a bit deeper into the language.

Jinx Status

Jinx was released as 1.0.0 (stable) on January 1, 2019, after three years of development, refinement, and testing. Since then, mostly minor language features and API enhancements have been edded. Additional work, for the immediate future, will be restricted to bug fixing or critical refinements, with the intention of retaining backwards compatibility.

jinx's People

Contributors

jamesboer avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

jinx's Issues

Calling a function from C++

Hi, is it possible to call a Jinx function from C++, in case of an event based API? And, ideally, check if the function was defined in a script in the first place?

Compilation fails in VS17

I think it would be important to note that compilation fails in VS17. Your fix for addressing the VisualStudio 2015 bug almost works for VisualStudio 2017.

On JxUnicode.cpp:188, the check should be #if _MSC_VER >= 1900. For VisualStudio 2017 (version 15.5.2) the value for _MSC_VER is 1912 and so it doesn't use your fix.

On a more positive note:
Your bytecode plays very well with binary serialization, which makes it easy to precompile scripts and ship bytecode instead of source code (and I can package the bytecode in my binary object archives so the bytecode is hidden from the user).

Segmentation fault on object destruction

Hello,

I have got Jinx up and running, now I'm trying to abstract it into a single class, so that I can compile and run a script a little easier.

I made the library, runtime, script and bytecode pointers into members, like so:

class ScriptWrapper {
private:
    Jinx::LibraryPtr m_library;
    Jinx::RuntimePtr m_runtime;
    Jinx::ScriptPtr m_script;
    Jinx::BufferPtr m_bytecode;

Then, I use a method compile the script, like so:

m_runtime = Jinx::CreateRuntime();

std::string scriptText;
// ... load the script from a file

m_library = m_runtime->GetLibrary("");

m_bytecode = m_runtime->Compile(scriptText.c_str());

if (!m_bytecode)
    return;

m_script = m_runtime->CreateScript(m_bytecode);

Then, I have another method that simply calls m_script->Execute();.
This works perfectly, until the object containing all this is destroyed. It prints two messages to the console, both saying: "Could not free block at shutdown. Memory still in use." and then segfaults from Jinx::Impl::BlockHeap::FreeBlock, at line 5,321 of Jinx.hpp. Is this a bug, or am I doing something wrong?

unable to complete CallFunction() with low instruction count

Thanks for Jinx — I love it and have been evaluating it for some time now. Incidentally, I have a number of typo corrections (both in code and the docs) that I'll try to submit sometime in the near future.

I'm working on a REPL of sorts using Jinx, so this might be a bit of an unusual case. I saw #19 which I thought would get me closer, but it's not quite the same situation.

Because I have a REPL that I want to be interruptible (with Esc), I have set a low globalParams.maxInstructions, to the tune of 10 or so. That way, when the user wants to interrupt, not very many additional instructions are executed before the keyboard buffer is checked, the request is seen, and the script is cancelled. With a higher number, the interrupting is less responsive since the script->Execute() call will run for a longer time.

Often, the scripts are procedural and straightforward. Other times, I have a use case where the user can (not required) define the (public) function tick() which, if found in a script, will be executed repeatedly in between double-buffered screen swaps and vertical sync waits. This is after the whole script is "executed", i.e. after this ticking environment is fully setup.

While I can check that the whole script is fully parsed and executed (and thus tick() is registered if present) by calling script->Execute() repeatedly and checking for script->IsFinished(), with the low instruction count, the later calls to CallFunction() on the found tick() don't necessarily run the whole function. That is, if the body of tick() is more instructions than e.g. 10, it will never complete.

Checking script->IsFinished() isn't appropriate here, since it is always "finished" at this point, and calling script->Execute() isn't either since it's already been executed fully. Also, the runtime parameters aren't meant to be changed after initial setup, so the instruction count can't be changed (and even if it could, making this a higher number would defeat the responsive cancelation needs mentioned above).

Do you have any tips or thoughts about this? I can't see a way in the current code that I should be accomplishing this, but I may be missing something. I feel like something akin to script->IsFinished() but for function bodies would be useful.

Feature: Coroutines

Coroutines/generator objects would be nice.

The issue in particular is calling functions that can themselves wait while waiting on them as well as knowing that the functions completed to accomplish a race.

-- this is a RACE, one coroutine will win the race
wait until (A_that_waits or B_that_waits)
if coro finished A_that_waits
    ... do stuff because A won the race
else if coro finished B_that_waits
    ... do stuff because B won the race
end

Or are there any workarounds that are close enough to a coroutine/generator?

Issue using `x key` and `x value` with literal in loop over

While playing around with the current state of this nice (and excellently documented!) language, I found the following issue:

Description

Using x key and x value interleaved with a literal within a single statement in a loop body results in error.

'get key' called with non-iterator param
'get value' called with non-iterator param

If I remove the literal in the statement it works fine.
E.g. the following works:

loop x over numbers
     foo x key, x value
end

while

loop x over numbers
     foo x key, 1, x value
end

does not
If I split the body into multiple statements it works as well.

Steps to reproduce

  • execute the script from the tutorial for Loop over Collections
set 'my list' to 5, 4, 3, 2, 1
loop x over 'my list'
    write line "key = ", x key, ", value = ", x value
end

Expected Behavior

The console shows the string "key = 1, value = 5" (...)

Observed Behavior

The console shows

'get key' called with non-iterator param
'get value' called with non-iterator param
nullnull

Affected Versions

function calls with string parameter by value

It seems that strings in a function call are passed by value:

import core

function test {str}
    set str[1] to "x"
end

set s to "Hello"
test s
write line s -- prints "Hello" unchanged

Is this intentional since strings are now mutable?
If so, wouldn't it make more sense (and be more consistent) to pass strings like collections by reference?
From the programmer's point of view, a string would then be a collection of strings.
This also applies to iterating over strings with "over".

Platform-Independent Precompiled Scripts

This isn't so much of an "issue" as it is a suggestion (or here's a way I've used this?).

Since Jinx compiles down to byte-code and we can directly access that byte-code, I've tried to think of a way of precompiling scripts.

I use cereal for serializing my game objects into archives, and it has a portable binary format that, as far as I've seen, doesn't seem to have any issues with endianness errors.

Now it's not the nicest solution, but it works as a proof on concept.
A class called BytecodeInstance stores compiled byte-code and serializes/deserializes it to/from binary archives, respectively.

The byte-code is just saved exactly as it is in memory, as an array of uint8_t elements.
It saves the length, too, of course. And cereal adds some bookkeeping data, so it's not exactly the same size as just the byte-code, but the bookkeeping data is minimal.

class BytecodeInstance
{
public:

  void Set(Jinx::BufferPtr Ptr)
  {
    if (!DataArray.empty())
      DataArray.clear();

    DataArray.resize(Ptr->Size());

    size_t pos = 0;
    Ptr->Read(&pos, DataArray.data(), DataArray.size());
  }

  Jinx::BufferPtr Get()
  {
    Jinx::BufferPtr Ptr = Jinx::CreateBuffer();

    size_t pos = 0;
    Ptr->Write(&pos, DataArray.data(), DataArray.size());

    return Ptr;
  }

  template<class Archive>
  void save(Archive & ar) const
  {
    ar(DataArray);
  }

  template<class Archive>
  void load(Archive & ar)
  {
    if (!DataArray.empty())
      DataArray.clear();

    ar(DataArray);
  }

private:

  std::vector<uint8_t> DataArray;

};

And a BytecodeArchive class maintains a collection of BytecodeInstances, and creates new archives from byte-code.

class BytecodeArchive
{
public:

  void Archive(Jinx::BufferPtr BufPtr)
  {
    ByteCodes.emplace_back();
    ByteCodes.back().Set(BufPtr);
  }

  template<class Archive>
  void save(Archive & ar) const
  {
    ar(ByteCodes);
  }

  template<class Archive>
  void load(Archive & ar)
  {
    ar(ByteCodes);
  }

  std::list<BytecodeInstance>& GetCodes()
  {
    return ByteCodes;
  }

private:

  std::list<BytecodeInstance> ByteCodes;

};

This is all assuming I didn't completely botch the usage of your API, but it hasn't crashed yet.

For testing these precompiled scripts, I registered 2 functions, in two different libraries.

Jinx::Variant Testfunc(Jinx::ScriptPtr Script, Jinx::Parameters Params)
{
  std::cout << Params[0].GetString() << "\n";
  return nullptr;
}

Jinx::Variant GetPlayerHealth(Jinx::ScriptPtr Script, Jinx::Parameters Params)
{
  return 100;
}

I had to generate the precompiled scripts once, so I did so by just using the same process one would for compiling scripts from source, then archiving the byte-code, then finally serializing the byte-codes into a binary archive on disk.

auto JinxRuntime = Jinx::CreateRuntime();

const char * src =
u8R"(import core
import 'testlib'
import 'gamecore'

set res to test ("some") func
write line gamecore player health

set gamecore player health to gamecore player health - 50

write line gamecore player health
)";

auto lib = JinxRuntime->GetLibrary("testlib");
lib->RegisterFunction(Jinx::Visibility::Public, {"test", "{string}", "func"}, Testfunc);

auto gamecore = JinxRuntime->GetLibrary("gamecore");
gamecore->RegisterProperty(Jinx::Visibility::Public, Jinx::Access::ReadWrite, "player health", 100);
gamecore->RegisterFunction(Jinx::Visibility::Public, {"get", "player", "health"}, GetPlayerHealth);

auto Compiled = JinxRuntime->Compile(src);
auto Script = JinxRuntime->CreateScript(Compiled);

const char * script2 = u8R"(
  loop from 1 to 10
    set a to 2
    set b to 3
    set c to a + b
    set d to b - a
    set e to a * b
    set f to b / 1
    set g to 10 % b
    set h to 123.456
    set i to 23.45
    set j to h * i
    set k to h / i
    set l to h + i
    set m to h - i
    set n to h % i
    wait
  end
)";

auto Bytecode2 = JinxRuntime->Compile(script2);

const char * script3 = u8R"(

  -- Simple if/else tests
  set a to false
  if true
    set a to true
  end
  set b to false
  if true
    set b to true
  else
    set b to false
  end
  set c to false
  if false
    set c to false
  else
    set c to true
  end
  set d to false
  if false
    set d to false
  else if true
    set d to true
  else
    set d to false
  end
  set e to false
  if false
    set e to false
  else if false
    set e to false
  else if true
    set e to true
  else
    set e to false
  end
  set f to false
  if false
    set f to false
  else if false
    set f to false
  else if false
    set f to false
  else
    set f to true
  end
  set g to false
  if true
    if true
      set g to true
    end
  end
  set h to false
  if false
  else
    if true
      set h to true
    end
  end
  )";

auto bytecode3 = JinxRuntime->Compile(script3);

const char * script4 = u8R"(
  import core
  loop from 1 to 10
    set a to [1, "red"], [2, "green"], [3, "blue"], [4, "yellow"], [5, "magenta"], [6, "cyan"]
    set b to a[1]
    set c to a[2]
    set d to a[3]
    set e to a[4]
    set a[5] to "purple"
    set a[6] to "black"
    loop i over a
      if i value = "blue"
        erase i
      end
    end
    wait
  end
  )";

auto bytecode4 = JinxRuntime->Compile(script4);

BytecodeArchive CodeArchive;

CodeArchive.Archive(Compiled);
CodeArchive.Archive(Bytecode2);
CodeArchive.Archive(bytecode3);
CodeArchive.Archive(bytecode4);  

// For the stress tests below, I looped and compiled additional copies
// of 'bytecode2'

std::ofstream ofile("codearchive.bytecode", std::ios_base::binary);

{
  cereal::PortableBinaryOutputArchive archive(ofile);

  archive(CodeArchive);
}

ofile.close();

After that point, I can just load it up easily (assuming I still added the native functions like above):

std::ifstream ifile("codearchive.bytecode", std::ios_base::binary);

BytecodeArchive codearchive;

{
  cereal::PortableBinaryInputArchive archive(ifile);

  archive(codearchive);
}

After that, the main program looks like:

Jinx::Variant Testfunc(Jinx::ScriptPtr Script, Jinx::Parameters Params)
{
  std::cout << Params[0].GetString() << "\n";
  return nullptr;
}

Jinx::Variant GetPlayerHealth(Jinx::ScriptPtr Script, Jinx::Parameters Params)
{
  return 100;
}

int main(int argc, char **argv)
{
  auto JinxRuntime = Jinx::CreateRuntime();

  auto lib = JinxRuntime->GetLibrary("testlib");

  lib->RegisterFunction(Jinx::Visibility::Public, {"test", "{string}", "func"}, Testfunc);

  auto gamecore = JinxRuntime->GetLibrary("gamecore");

  gamecore->RegisterProperty(Jinx::Visibility::Public, Jinx::Access::ReadWrite, "player health", 100);
  gamecore->RegisterFunction(Jinx::Visibility::Public, {"get", "player", "health"}, GetPlayerHealth);

  std::ifstream ifile("codearchive.bytecode", std::ios_base::binary);

  BytecodeArchive codearchive;

  {
    cereal::PortableBinaryInputArchive archive(ifile);

    archive(codearchive);
  }

  auto & codes = codearchive.GetCodes();

  Jinx::BufferPtr code = codes[0].Get();

  std::vector<Jinx::ScriptPtr> Scripts;

  for (auto & bcode : codes)
  {
    Scripts.push_back(JinxRuntime->CreateScript(bcode.Get()));
  }

Maybe it sounds trivial, or not essential, but it drastically speeds up loading times when booting up the engine.

The binary archives are, for the most part, unreadable, but most literal strings remain in-tact.

For the example above, the following is generated (if viewed as a hex dump) for the binary archive (for a single copy of the 4 scripts):

0104 0000 0000 0000 006f 0000 004a 494e
5800 0001 0017 0000 0000 002a 0404 0000
0073 6f6d 6500 02cd 1ae7 049a 2ae6 1932
688b 1175 74fd 89c1 2541 8653 34e9 ea73
ec02 63d3 3cbd c379 7aba 1f25 4186 5334
e9ea 73ec 2a02 3200 0000 0000 0000 3430
4186 5334 e9ea 73ec 2541 8653 34e9 ea73
ec02 63d3 3cbd c379 7aba 1f0b 9601 0000
4a49 4e58 0000 0100 1700 0000 0000 2d2a
0201 0000 0000 0000 002a 020a 0000 0000
0000 002a 002d 2a02 0200 0000 0000 0000
3202 c07d 348f d965 a72a 0203 0000 0000
0000 0032 1c83 43ae cca1 97c3 2802 c07d
348f d965 a728 1c83 43ae cca1 97c3 0032
aedd 793a b973 3815 281c 8343 aecc a197
c328 02c0 7d34 8fd9 65a7 3432 0462 3f68
be5c 5ed3 2802 c07d 348f d965 a728 1c83
43ae cca1 97c3 1b32 cbb3 b64c c9ba 5643
281c 8343 aecc a197 c32a 0201 0000 0000
0000 0005 32be b415 5d8b 09b3 002a 020a
0000 0000 0000 0028 1c83 43ae cca1 97c3
1a32 7b3a 085a d733 00d2 2a01 77be 9f1a
2fdd 5e40 32d8 33f7 52ea 1d27 a12a 0133
3333 3333 7337 4032 22bf d80a 1b7e 1964
28d8 33f7 52ea 1d27 a128 22bf d80a 1b7e
1964 1b32 a808 4ac4 6ac6 443c 28d8 33f7
52ea 1d27 a128 22bf d80a 1b7e 1964 0532
77e8 e836 0c73 115f 28d8 33f7 52ea 1d27
a128 22bf d80a 1b7e 1964 0032 6c26 6c49
4d13 6317 28d8 33f7 52ea 1d27 a128 22bf
d80a 1b7e 1964 3432 0ff1 9c95 f1ed 3d10
28d8 33f7 52ea 1d27 a128 22bf d80a 1b7e
1964 1a32 a0e8 00cc 92df 72fc 362e 1813
2500 0000 2e0b 2002 0000 4a49 4e58 0000
0100 1700 0000 0000 2a03 0032 02c0 7d34
8fd9 65a7 2a03 0111 3000 0000 2d2a 0301
3202 c07d 348f d965 a72e 2a03 0032 1c83
43ae cca1 97c3 2a03 0111 5700 0000 2d2a
0301 321c 8343 aecc a197 c32e 1065 0000
002d 2a03 0032 1c83 43ae cca1 97c3 2e2a
0300 32ae dd79 3ab9 7338 152a 0300 118c
0000 002d 2a03 0032 aedd 793a b973 3815
2e10 9a00 0000 2d2a 0301 32ae dd79 3ab9
7338 152e 2a03 0032 0462 3f68 be5c 5ed3
2a03 0011 c100 0000 2d2a 0300 3204 623f
68be 5c5e d32e 10ea 0000 002a 0301 11dc
0000 002d 2a03 0132 0462 3f68 be5c 5ed3
2e10 ea00 0000 2d2a 0300 3204 623f 68be
5c5e d32e 2a03 0032 cbb3 b64c c9ba 5643
2a03 0011 1101 0000 2d2a 0300 32cb b3b6
4cc9 ba56 432e 1055 0100 002a 0300 112c
0100 002d 2a03 0032 cbb3 b64c c9ba 5643
2e10 5501 0000 2a03 0111 4701 0000 2d2a
0301 32cb b3b6 4cc9 ba56 432e 1055 0100
002d 2a03 0032 cbb3 b64c c9ba 5643 2e2a
0300 32be b415 5d8b 09b3 002a 0300 117c
0100 002d 2a03 0032 beb4 155d 8b09 b300
2e10 c001 0000 2a03 0011 9701 0000 2d2a
0300 32be b415 5d8b 09b3 002e 10c0 0100
002a 0300 11b2 0100 002d 2a03 0032 beb4
155d 8b09 b300 2e10 c001 0000 2d2a 0301
32be b415 5d8b 09b3 002e 2a03 0032 7b3a
085a d733 00d2 2a03 0111 ec01 0000 2d2a
0301 11eb 0100 002d 2a03 0132 7b3a 085a
d733 00d2 2e2e 2a03 0032 d833 f752 ea1d
27a1 2a03 0011 0702 0000 2d2e 101f 0200
002d 2a03 0111 1e02 0000 2d2a 0301 32d8
33f7 52ea 1d27 a12e 2e0b c101 0000 4a49
4e58 0000 0100 1700 0000 0000 2d2a 0201
0000 0000 0000 002a 020a 0000 0000 0000
002a 002d 2a02 0100 0000 0000 0000 2a04
0300 0000 7265 6400 2a02 0200 0000 0000
0000 2a04 0500 0000 6772 6565 6e00 2a02
0300 0000 0000 0000 2a04 0400 0000 626c
7565 002a 0204 0000 0000 0000 002a 0406
0000 0079 656c 6c6f 7700 2a02 0500 0000
0000 0000 2a04 0700 0000 6d61 6765 6e74
6100 2a02 0600 0000 0000 0000 2a04 0400
0000 6379 616e 0022 0600 0000 3202 c07d
348f d965 a72a 0201 0000 0000 0000 0029
02c0 7d34 8fd9 65a7 321c 8343 aecc a197
c32a 0202 0000 0000 0000 0029 02c0 7d34
8fd9 65a7 32ae dd79 3ab9 7338 152a 0203
0000 0000 0000 0029 02c0 7d34 8fd9 65a7
3204 623f 68be 5c5e d32a 0204 0000 0000
0000 0029 02c0 7d34 8fd9 65a7 32cb b3b6
4cc9 ba56 432a 0205 0000 0000 0000 002a
0406 0000 0070 7572 706c 6500 3302 c07d
348f d965 a72a 0206 0000 0000 0000 002a
0405 0000 0062 6c61 636b 0033 02c0 7d34
8fd9 65a7 2d28 02c0 7d34 8fd9 65a7 2711
b601 0000 2332 22bf d80a 1b7e 1964 2d28
22bf d80a 1b7e 1964 02c1 86be eba1 7309
5f2a 0404 0000 0062 6c75 6500 0611 af01
0000 2d09 22bf d80a 1b7e 1964 2e2e 1911
8001 0000 2e36 2e18 1325 0000 002e 0b

(where your signature "JINX" header is sitting inconspicuously on the first and second line 004a 494e 5800, and at the beginning of the byte-code for every script saved).

Looking at it as UTF-8 instead of a hex dump (in Notepad):

��       o   JINX  � �     *��   some ������*��2h��ut���%A�S4��s��c�<��yz��%A�S4��s�*�2       40A�S4��s�%A�S4��s��c�<��yz�����  JINX  � �     -*��       *�
       * -*��       2��}4��e�*��       2��C�̡��(��}4��e�(��C�̡�� 2��y:�s8�(��C�̡��(��}4��e�42�b?h�\^�(��}4��e�(��C�̡���2˳�LɺVC(��C�̡��*��       �2���]�	� *�
       (��C�̡���2{:�Z�3 �*�w���/�^@2�3�R��'�*�33333s7@2"��
�~�d(�3�R��'�("��
�~�d�2��J�j�D<(�3�R��'�("��
�~�d�2w��6s�_(�3�R��'�("��
�~�d 2l&lIM�c�(�3�R��'�("��
�~�d42����=�(�3�R��'�("��
�~�d�2�� ̒�r�6.��%   .� �  JINX  � �     *� 2��}4��e�*���0   -*��2��}4��e�.*� 2��C�̡��*���W   -*��2��C�̡��.�e   -*� 2��C�̡��.*� 2��y:�s8�*� ��   -*� 2��y:�s8�.��   -*��2��y:�s8�.*� 2�b?h�\^�*� ��   -*� 2�b?h�\^�.��   *����   -*��2�b?h�\^�.��   -*� 2�b?h�\^�.*� 2˳�LɺVC*� ���  -*� 2˳�LɺVC.�U�  *� �,�  -*� 2˳�LɺVC.�U�  *���G�  -*��2˳�LɺVC.�U�  -*� 2˳�LɺVC.*� 2���]�	� *� �|�  -*� 2���]�	� .���  *� ���  -*� 2���]�	� .���  *� ���  -*� 2���]�	� .���  -*��2���]�	� .*� 2{:�Z�3 �*�����  -*�����  -*��2{:�Z�3 �..*� 2�3�R��'�*� ���  -.���  -*�����  -*��2�3�R��'�..���  JINX  � �     -*��       *�
       * -*��       *��   red *��       *��   green *��       *��   blue *��       *��   yellow *��       *��   magenta *��       *��   cyan "�   2��}4��e�*��       )��}4��e�2��C�̡��*��       )��}4��e�2��y:�s8�*��       )��}4��e�2�b?h�\^�*��       )��}4��e�2˳�LɺVC*��       *��   purple 3��}4��e�*��       *��   black 3��}4��e�-(��}4��e�'���  #2"��
�~�d-("��
�~�d�����s	_*��   blue ����  -	"��
�~�d..����  .6.��%   .�

It looks different in Sublime. Go figure. I can shove that through a base64 encoder and obfuscate it, but that may be more hassle than its worth. There is no compression, I should add. It's just saved in raw binary.

Since Jinx stresses heavy testing, I tested compiling:

  • 400 scripts
  • 1400 scripts (excessive, yes, but why not)

To avoid performance drops from too many allocations, I used Boost's memory pools (I did not change Jinx's custom allocator with global params - it always got slower when I did, so I decided to just leave it be).

400 scripts

From Source: 6.17897 seconds
From Precompiled Byte-Code: 0.025639 seconds
Archive Size: 218KB

Overall: 0.4% as much time.

1400 scripts

From Source: 21.8528 seconds
From Precompiled Byte-Code: 0.084868 seconds
Archive Size: 757KB

Overall: 0.3% as much time.


Anyway, to end a long post, I thought I'd share how I'm packaging scripts so that I only have to compile my scripts once.

Added benefit: If there's a bug in a script, all I have to do is remake the .bytecode file and patch that instead of the entire executable.

As for the strings remaining readable, I initially got the inspiration for serializing everything into game object archives from Unreal Engine. I've studied relatively closely how their serialization works, and many of their literal strings remain in-tact when serialized into shipped content (you can also see the names of the member variables of their classes in the object archives). So I imagine it's not an issue most people are really that concerned with.

enum and strings

Hello again.

Am doing a bit of integration to kick the tires and am wondering if there is a good way to handle enum mapping? From what I can tell, casting to/from a jinx integer seems to be the only option, which creates a safety exposure when casting back to the native enum.

Was also wondering about jinx strings. It seems operations are limited to reading and overwriting a given index. Not looking for the rich set of algorithms and manipulation that C++ allows, but, as a suggestion, at least the basics that C allows?

Unable to wait for coroutines to finish inside of functions

Hi, I'm looking to integrate Jinx as a primary language game-logic wise for my prototype engine due to its cooperative asynchronous like nature, so it's going to involve a lot of function calling, mostly from the C++ side (especially one that's called upon boot-up to initialize and load everything up).

But I have found out that I can not have a calling function wait for another coroutine to finish, while waiting in the script's main body (outside a function) works as intended. I think it has to do with a script being marked as "finished" after calling ExecuteScript before a function could be even called with CallFunction. Especially since a typical script for this planned engine will be nothing but functions and coroutines.

Other languages like JavaScript and Python can await for coroutines to finish inside of functions, but if Jinx couldn't, what's the point?

C++:

auto script = _runtime->ExecuteScript(file.c_str(), nullptr, "boot.jinx");
    
auto id = script->FindFunction(nullptr, "boot {}");
script->CallFunction(id, {param});

if (script->IsFinished()) {
    TraceLog(LOG_INFO, "How is it finished???");
}

boot.jinx:

import core

function hello
    loop from 1 to 10
        write line "Hello"
        wait
    end
end

private function boot {integer param}
    -- other stuff...

    set c to async call function hello
    write line "Waiting..."
    wait until c is finished
    -- Lines below will never be reached...
    write line "Finished waiting"
end

Output:

Hello
Waiting...
Hello
INFO: How is it finished???

Feature: Functions as first class objects

Feature Request

Add a possibility to pass functions as parameters and store them in variables and collections.
This allows the use of abstract algorithms like map, filter, reduce,...

This feature request is not about anonymous functions aka closures / lambdas.

The natural language like style, where parameters are interleaved in the function signature and call makes implementation of this feature more difficult than in other languages.

Possible limited solutions and workarounds:

  • Only allow unary functions with signature "name {parameter}".
  • A string eval function

Use Cases

Allow users to write abstract algorithms which depend on behavior injected by the caller.
Example: sorting a collection of collections by an arbitrary element.

I am working on a proof of concept to include Jinx as a command and scripting language in lighting control software.
I would like to enable users to write their own transformations and filter functions to select objects by arbitrary criteria.

Examples in other languages

  • C allows passing of function pointers as parameters. In particular the qsort sorting function uses this feature.
  • lua stores functions in tables.
  • C++ has multiple such features including the generic std::function wrapper.
  • Functional programming languages are essentially built on this concept.

Related features: #11

Git tags for releases

Hi, I noticed that Jinx doesn't have tags for any releases. Would you be able to add tags, or possibly a tag for the latest version 1.1.8?

SSL certificate has expired for jinx-lang.org

$ curl https://www.jinx-lang.org/
curl: (60) SSL certificate problem: certificate has expired
More details here: https://curl.haxx.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.

Newbie help with functions

Hi

Just playing around with Jinx and I am stuck at trying to call a host function from a script.


	jinxRuntime = Jinx::CreateRuntime();

	const auto library = jinxRuntime->GetLibrary("editjinx");
	auto result = library->RegisterFunction(Jinx::Visibility::Public, "host print {} value", hostPrint);
	if (!result)
		printf("Error registering function in script.\n");
	else
		printf("Registered function in script 'host print {} value'\n");

and the hostPrint function is

Jinx::Variant hostPrint(const Jinx::ScriptPtr &script, const Jinx::Parameters &params)
{
	printf("Host : %i\n", params[0]);

	return nullptr;
}

and the script is

-- Use the core library
import core

-- Write to the debug output
write line "Hello world"

host print 999

and I get the error

Error at 
line 7, column 1: 
Unknown symbol in statement

host 
print 
999 

 ^^^^^^^^^^^^^^

I can't see why this won't work - any advice would be great.

Thanks

Performance benchmarks?

Hi. This looks promising.

I read the doc on performance, but am curious if you've benchmarked Jinx against other embedded languages for a handful of common computations (e.g., fibonacci sequence)?

Question: Working with strings

Hi,

first of all my congratulations for this really cool scripting language - especially the function declarations and the multi word variables: I have never seen anything like this before.

My question is: How to access the content of a string? Something like this:

set s to "Hello"
set c to s[1]
loop x over s
    write line "key = ", x key, ", value = ", x value
end

non-local functions added to internal function list twice

I noticed that if you declare a public or private function in a script, both the JxRuntime::CreateScript() step (and other compilations) and then the actual JxScript::Execute() step add the function signature to the script library's internal JxLibrary::m_functionList. I've confirmed this with a debugger and breaking on JxLibrary::RegisterFunctionSignature(), where both functions hit the breakpoint.

Compilation backtrace:

Jinx::Impl::Library::RegisterFunctionSignature
Jinx::Impl::Parser::ParseFunctionDefinition
Jinx::Impl::Parser::ParseStatement
Jinx::Impl::Parser::ParseScript
Jinx::Impl::Parser::Execute
Jinx::Impl::Runtime::Compile
Jinx::Impl::Runtime::Compile
Jinx::Impl::Runtime::CreateScript

Execution backtrace:

Jinx::Impl::Library::RegisterFunctionSignature
Jinx::Impl::Script::Execute

Though I don't see any side effects to this, it seems like the simple addition of a call to m_library->FunctionSignatureExists(signature) inside of JxScript.cpp's handling of Opcode::Function takes care of this.

The whole reason this happens is because in that handling, once the scope is determined to not be VisibilityType::Local, the function gets added to the library.

logo proposal

Hi @JamesBoer I am a graphic artist and i would like offer your project a logo design for free. Will wait for your permission first.Best regards!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.