Jancy Language
Jancy is a new language heavily influenced and inspired by both Java and C++. It takes the best from the two worlds and blends it together, adding its own share of innovative features. It’s a next-generation scripting language perfectly suited for both IO and UI programming. One of the primary motivations for creating Jancy was IO Ninja.
ABI compatibility with C/C++
Jancy is ABI-compatible with C/C++ (ABI stands for application binary interface). This has two major benefits.
Firstly, it simplifies and at the same time increases effectiveness of the application-to-script interaction. There is no need to pack data into variant-like containers and push them on the VM stack before calling a user script function – instead, you call it directly just like you’d call any other function in your program: Jancy understands all major calling conventions.
Secondly, it allows copy-pasting C definitions of protocol headers. C is the de-facto standard language for system programming and it’s possible to find the C language implementation of virtually any protocol in existence. Need to use this protocol from Jancy for analyzing/re-implementing/testing? Copy-paste the protocol header definition, and Jancy will most likely eat it. Even if not (of course, there are differences between C and Jancy syntax), the required changes should be minimal. This could be particularly useful considering another prominent feature of IO Ninja, the packet builder.
Pointers
Jancy provides a feature that few other languages have: safe data pointers. It gives you an opportunity to safely “walk” across the data buffers with pointer arithmetic, analyzing or building a packet.
io.IpHdr const* ipHdr = (io.IpHdr const*)(p + baseOffset);
switch (ipHdr.m_protocol) {
case io.IpProtocol.Icmp:
processIcmpHdr(p, baseOffset + sizeof(io.IpHdr));
break;
case io.IpProtocol.Tcp:
processTcpHdr(p, baseOffset + sizeof(io.IpHdr));
break;
case io.IpProtocol.Udp:
processUdpHdr(p, baseOffset + sizeof(io.IpHdr));
break;
}
There is no better way to deal with binary data than pointers and pointer arithmetic. But even if there was, the argument from the previous section still holds: C is the standard of system programming, therefore, it’s easy to find C language definitions of protocol headers.
Another pointer-related feature of Jancy that comes in handy in multi-threaded IO programming is function pointers. Jancy function pointers support partial application and scheduling.
Partial application means that you can capture “context” arguments for your callback, such as event handler, completion routine, etc.
Scheduling means you can assign a user-defined “scheduler” which will ensure execution of your callback in the correct environment (in the specific worker thread, under the lock, as a Window Message handler and so on).
EventContext* context = new EventContext;
context.m_socket = socket;
context.m_requestId = requestId;
// capture context and schedule it to be run in worker thread
socket.m_onSocketEvent += onSocketEvent~(context) @ m_workerThreadScheduler;
...
void m_onSocketEvent(
EventContext* context,
SocketEventParams const* params
) {
// we are in worker thread, handle socket event
}
Properties
Having properties is not a unique feature of Jancy — other languages have them, too. But we are sure no other language have properties implemented as good as Jancy. Jancy provides all colors and flavor of properties you could imagine:
- Indexed properties have syntax of arrays
- Bindable properties notify subscribers when property has changed
- Bindable data for auto-generating properties which do not need setters (and only needed for tracking changes)
- Autoget properties can bypass the getter and access the “value” field directly
- Simplified syntax form for declaring basic properties in the most natural way possible
- Full syntax form for declaring properties of arbitrary complexity
Properties are generally useful, as they make code much cleaner and more natural-looking. But they come especially handy in UI programming.
opaque class ComboBox {
...
bool autoget property m_isEditable;
property m_editText {
char const* autoget m_value;
set(char const* value);
alias bindable event m_onPropChanged() = m_onChanged;
}
char const* autoget property m_toolTipText;
uint_t autoget property m_backgroundColor;
char const* indexed property m_itemText(size_t index);
property m_currentText {
char const* get();
alias bindable event m_onPropChanged() = m_onChanged;
}
event m_onChanged();
event m_onEnter();
...
}
Reactive Programming
Jancy is one of the few imperative languages with the support of reactive programming. When explaining what reactive programming is, the best example would be Microsoft Excel.
Everybody used Excel. Everybody knows when you write a “formula” in cell A and refer to other cells B and C, the dependencies are being built automatically. You change B or C, and A gets updated. You do not need to write event handlers that would be invoked when B or C changes, and in these handlers update all the dependent cells. Sounds ridiculous, right? Who would do that?
Well, that’s what UI programmers do. UI widgets provide events that fire when certain properties change, and if you need to track these changes and do something in response – you write an event handler, subscribe for the event, and update dependent controls/value from within that handler.
Jancy brings that Excel-like automatic execution of “formulas” when values referred from that formula change. How does Jancy know when to use Excel-like execution and when to use the traditional imperative approach? Reactors. You declare dedicated sections of reactive code, so-called reactors. Expressions within reactors behave like formulas in Excel and get automatically re-evaluated when bindable properties, referred from the given expression change. All the dependency building/subscribing/unsubscribing is happening automatically behind the scene.
reactor TcpConnectionSession.m_uiReactor {
m_title = $"TCP $(m_addressCombo.m_editText)";
m_isTransmitEnabled = m_state == State.Connected;
m_actionTable [ActionId.Disconnect].m_isEnabled = m_state != State.Closed;
m_adapterProp.m_isEnabled = m_useLocalAddressProp.m_value;
m_localPortProp.m_isEnabled = m_useLocalAddressProp.m_value;
}
construct() {
...
m_uiReactor.start();
}
Of course, sometimes reactive approach doesn’t quite cut it, so you always have the traditional event handler approach at your disposal. Still, reactors might help here, too: Jancy provides a natural syntax of declaring in-reactor event handlers which will subscribe/unsubscribe automatically when reactor starts/stops.
int bindable m_state;
ComboBox* m_comboBox;
reactor m_reactor() {
onevent (bindingof(m_state))() {
// handle state change
}
onevent m_comboBox.m_onEnter() {
// handle enter hit in combobox
}
}
More
Above we tried to outline the Jancy features that are especially important for IO Ninja: all of the mentioned features are heavily used there and you are more than welcome to explore the script sources and see for yourself how it makes code so much more natural and elegant.
But of course, that doesn’t wrap up the list of Jancy innovations. If you want to learn more, please visit the dedicated Jancy website at: http://jancy.org