The first and only scripting language with safe pointer arithmetics, high level of ABI and source compatibility with C, spreadsheet-like reactive programming, built-in lexer generator, and more.
Switch branches/tags
Clone or download
Latest commit dfab486 Dec 14, 2018
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
ci [ci] fix: use a previous sphinx version (1.7.9) -- the current (1.8.0… Sep 19, 2018
cmake [cmake] added JANCY_JNCX_DIR to jancy_config.cmake Sep 6, 2018
doc [cmake] renamed: CONFIGURATION_SUFFIX -> CONFIGURATION; CONFIGURATION… May 17, 2018
include [jnc_api] fix: incorrect check re support for __has_attribute (ms_str… Dec 14, 2018
license [all] everything is prepared to inject licenses into source files Oct 20, 2016
samples [jnc] updated reactor samples/tests to the new syntax Oct 15, 2018
sphinx [jnc_sphinx] updated jancy lexer for pygments Apr 10, 2017
src [jnc_ct] fix: implemented CallConv_msc32 (the ABI test was failing on… Dec 14, 2018
test [test] added io_Modbus.jnc Dec 14, 2018
tools/jnc2cpp [jnc2cpp] exclude C-comments from the generated source Feb 9, 2017
website [cmake] renamed: CONFIGURATION_SUFFIX -> CONFIGURATION; CONFIGURATION… May 17, 2018
.appveyor.yml [travis][appveyor] added webhook notifications Jun 12, 2017
.gitignore standard .gitignore Oct 26, 2016
.travis.yml [travis] updated github links Jul 27, 2017
CMakeLists.txt [cpack] inverted the os-version order in the file name of a package (… May 18, 2018
LICENSE.txt [travis] only build docs on llvm-3.4.2 Jul 23, 2017
README.rst [travis] build docs on linux instead of osx Dec 19, 2017
dependencies.cmake [jnc_io] added tdemon-based device monitoring extensions library io_d… Dec 29, 2017
settings.cmake [axl][ecckey][doxyrest][graco][jancy] license headers injected Oct 21, 2016
version.cmake [jancy] release 1.8.5 Dec 14, 2018

README.rst

Jancy

https://travis-ci.org/vovkos/jancy.svg?branch=master https://ci.appveyor.com/api/projects/status/01gq23xd13twr8l5?svg=true

Abstract

Jancy

Jancy is the first and only scripting language with safe pointer arithmetics, high level of ABI and source compatibility with C, support for spreadsheet-like reactive programming, built-in generator of incremental lexers/scanners, dynamic structures with array fields of non-constant sizes, dual error handling model which allows you to choose between error-code checks and throw semantics at each call-site, and a lot of other unique and really useful features.

Design Principles

  • Statically typed C-family scripting language aimed at IO and UI

    Python is the scripting language of hackers. I hope Jancy will become the scripting language of those hackers who prefer to stay closer to C.

  • High level of ABI and source compatibility with C

    Calling from Jancy to native code and vice versa is as easy and efficient as it gets. So is developing Jancy libraries in C/C++ and Jancy bindings to popular libraries. So is porting publicly available algorithms from C to Jancy -- copy-paste often suffices!

  • Automatic memory management via accurate GC

    Losing manual memory management (together with the vast class of bugs and leaks associated with it) in favor of the GC employment has its price, but for scripting languages, it's 100% worth it.

  • LLVM as a back-end

    This was a no-brainer from the very beginning. I started with LLVM 3.1 five years ago; at the present moment Jancy builds and runs with any LLVM version from 3.4.2 all the way up to 5.0.0

Key Features

Safe Pointers and Pointer Arithmetic

Use pointer arithmetic -- the most elegant and the most efficient way of parsing and generating binary data -- and do so without worrying about buffer overruns and other pointer-related issues!

IpHdr const* ipHdr = (IpHdr const*) p;
p += ipHdr.m_headerLength * 4;

switch (ipHdr.m_protocol)
{
case Proto.Icmp:
        IcmpHdr const* icmpHdr = (IcmpHdr const*) p;
        switch (icmpHdr.m_type)
        {
        case IcmpType.EchoReply:
                // ...
        }
        // ...
}

If bounds-checks on a pointer access fail, Jancy runtime will throw an exception which you can handle the way you like.

Spreadsheet-like Reactive Programming

Write auto-evaluating formulas just like you do in Excel -- and stay in full control of where and when to use this spreadsheet-likeness:

reactor m_uiReactor ()
{
        m_title = $"Target address: $(m_addressCombo.m_editText)";
        m_isTransmitEnabled = m_state == State.Connected;
        // ...
}

m_uiReactor.start ();
// ...
m_uiReactor.stop ();

This, together with the developed infrastructure of properties and events, is perfect for UI programming!

Incremental Regex-based Switches

Create efficient regex-based switches for tokenizing string streams:

jnc.RegexState state;
reswitch (state, p, length)
{
case "foo":
        // ...
        break;

case r"bar(\d+)":
        print ($"bar id: $(state.m_subMatchArray [0].m_text)\n");
        break;

case r"\s+":
        // ignore whitespace
        break;

// ...
}

This statement will compile into a table-driven DFA which can parse the input string in O(length) -- you don't get any faster than that.

But there's more -- the resulting DFA recognizer is incremental, which means you can feed it the data chunk-by-chunk when it becomes available (e.g. once received over the network).

Dynamic Structs

Define dynamically laid-out structures with non-constant sizes of array fields -- this is used in many file formats and network protocol headers (i.e. the length of one field depends on the value of another):

dynamic struct FileHdr
{
        // ...
        char m_authorName [strlen (m_authorName) + 1];
        char m_authorEmail [strlen (m_authorEmail) + 1];
        uint8_t m_sectionCount;
        SectionDesc m_sectionTable [m_sectionCount];
        // ...
}

In Jancy you can describe a dynamic struct, overlap your buffer with a pointer to this struct and then access the fields at dynamic offsets normally, just like you do with regular C-structs:

FileHdr const* hdr = buffer;

displayAuthorInfo (hdr.m_authorName, hdr.m_authorEmail);

for (size_t i = 0; i < hdr.m_sectionCount; i++)
{
        processSection (hdr.m_sectionTable [i].m_offset, hdr.m_sectionTable [i].m_size);
}

You can write to dynamic structs, too -- just make sure you fill it sequentially from top to bottom. And yes, dynamically calculated offsets are cached, so there is no significant performance penalty for using this facility.

Scheduled Function Pointers

Assign a scheduler before passing a function pointers as a callback of some sort (completion routine, event handler, etc). This way you can elegantly place the execution of your callback in the correct environment -- for example, in the context of a specific thread:

class WorkerThread: jnc.Scheduler
{
        override schedule (function* f ())
        {
                // enqueue f and signal worker thread event
        }
        // ...
}

Then you apply a binary operator @ (reads: at) to create a scheduled pointer to your callback:

void onComplete (bool status)
{
        // we are in the worker thread
}

startTransaction (onComplete @ m_workerThread);

When the transaction completes and completion routine is finally called, onComplete is guaranteed to be executed in the context of the assigned m_workerThread.

Dual Error Handling Model

Both throw-catch and error-code approaches have their domains of application. Why force developers to choose one or another at the API design stage?

In Jancy you can write methods which can be both error-checked and caught exceptions from -- depending on what is more convenient at each particular call-site!

class File
{
        bool errorcode open (char const* fileName);
        close ();
        alias dispose = close;
}

Use throw-catch semantics:

foo (File* file)
{
        file.open ("data.bin");
        file.write (hdr, sizeof (hdr));
        file.write (data, dataSize);
        // ...

catch:
        print ($"error: $!\n");

finally:
        file.close ();
}

...or do error-code checks where it works better:

bar ()
{
        disposable File file;
        bool result = try file.open ("data.bin");
        if (!result)
        {
                print ($"can't open: $!\n");
                // ...
        }

        // ...
}

On a side note, see how elegantly Jancy solves the problem of deterministic resource release? Create a type with a method (or an alias) named dispose -- and every disposable instance of this type will get dispose method called upon exiting the scope (no matter which exit route is taken, of course).

Dual Type Modifiers

Jancy introduces yet another cool feature called dual type modifiers -- i.e. modifiers which have different meaning depending on the context. One pattern dual modifiers apply really well to is read-only fields:

class C
{
        int readonly m_readOnly;
        foo ();
}

The readonly modifier's meaning depends on whether a call-site belongs to the private-circle of the namespace:

C.foo ()
{
        m_readOnly = 10; // ok
}

bar (C* c)
{
        print ($"c.m_readOnly = $(c.m_readOnly)\n"); // ok
        c.m_readOnly = 20; // error: cannot store to const-location
}

No more writing dummy getters!

Another common pattern is a pointer field which inherits mutability from its container:

struct ListEntry
{
        ListEntry cmut* m_next;
        variant m_value;
}

The cmut modifier must be used on the type of a member -- field, method, property. The meaning of cmut then depends on whether the container is mutable:

bar (
        ListEntry* a,
        ListEntry const* b
        )
{
        a.m_next.m_value = 10; // ok
        b.m_next.m_value = 10; // error: cannot store to const-location
}

Implementing the equivalent functionality in C++ would require a private field and three accessors!

Finally, the most obvious application for dual modifiers -- event fields:

class C1
{
        event m_onCompleted ();
        work ();
}

The event modifier limits access to the methods of the underlying multicast depending on whether a call-site belongs to the private-circle of the namespace:

C.work ()
{
        // ...
        m_onCompleted (); // ok
}

foo (C* c)
{
        c.m_onCompleted += onCompleted; // adding/remove handlers is ok
        c.m_onCompleted (); // error: non-friends can't fire events
}

Other Notable Features

  • Multiple inheritance
  • Properties -- the most comprehensive implementation thereof!
  • Weak events (which do not require to unsubscribe)
  • Partial application for functions and properties
  • Function redirection
  • Extension namespaces
  • Thread local storage
  • Bitflag enums
  • Big-endian integers
  • Perl-style formatting
  • Hexadecimal, raw and multi-line literals
  • Opaque classes
  • break<n>, continue<n>

...and many other cool and often unique features, which simply can't be covered in the quick intro.

Documentation