Charon is a lisp-like, simple language that compiles to Lua. Lua is a great programming language for embed systems, IoT and general purpose scripting. Is widely used, is small and fast. Charon is an attempt to take those traits, and provide the programmer with a functional-style, more robust and with a touch of LISP programming.

Charon only needs a small runtime for new types and functions, which also can be embed. The key of Charon development is to maintain always a strict interoperability with Lua systems, so existing codebase can be maintained, and binding code is easier to write.

Currently, Charon is under heavy development, and many aspects have to be defined yet.

Latest version

Release Badge

Overview

Currently, the produced code is ugly and spans less lines. This will change in future releases.

Installation

Installation methods are being improved right now, nothing is stable yet, so expect this to change in the future.

Windows

Right now installing Charon for Windows is as simple as downloading the binaries of the latest build, plus registering it's location to the path. You can download them at the releases page.

Other

No packages are distributed yet for other OSes, so the best solution is to build from source.

git clone https://github.com/sigmasoldi3r/charon-lang.git

After that, you can just run, in case of you having Yarn:

yarn install && yarn build && yarn dist

After that, you can just run, in case of you using npm:

npm install && npm run build && npm run dist

Remember to add the binaries to your path, the last command will generate them on the dist/ folder. If you can't or don't want to generate binaries, you can just use the produced Javascript file.

Usage

Command Line

If the compiler is installed, you should have access to the command charon, which can compile .crn files into .lua files. At this moment, no REPL is available to test charon directly. charon command options are: Options:
Option Description
--help Show help
--output, -o Output file, optional.
--embed-runtime, -e Embeds the runtime instead of requiring it. Use for standalone environments.
--extract-runtime, -x Extracts the runtime to make it available in your local environment.
--global-export, -g Treats all exported modules as global symbols, including charon runtime.
--no-runtime, -n Makes compiled modules not require charon runtime explicitly.
--variadic-closures, -s  Makes all immediate closures variadic (if, do and such blocks).
--type, -t Compilation mode, will tell how the file is produced. CURRENTLY SUPPORTS MODULE ONLY!
--config, -c Configuration file for batch compile of projects. NOT USED CURRENTLY.
--version Show version number

Example

Compile your main script main.crn into a Lua script: charon main.crn

API Usage

You can use some parts of the compiler as an embed compiler, like in web browsers, or in a Node.JS application.

There is not much focus on API usage yet, but you can import the individual modules from build/src/ folder once built.

Include build/src/Compiler.js file, (Or in case of a Typescript project src/Compiler.ts). Then you can simply run the Compiler#compileFile method:

Compiler.compileFile('input.crn', 'output.lua');

If you have access to the Node.JS filesystem. Else you can build your own instance of the compiler, and avoid using the static compile method:


              const instance = Compiler.create({ ...presets.module });
              
 const out = instance.compile('input source', 'source name');

Language Reference

First of all, Charon uses a Lisp-like syntax. If you're familiar with any Lisp family language, specially the Clojure family, it will be easier to understand.

This implies that the language uses Prefix or Polish notation. This translates, in practical terms, to the idea that almost every Charon expression will come in a prefix notation, instead of infix or postfix, and surrounded by parenthesis.

In most languages, binary operators are infix, you tipically do: 2 + 2. In Charon instead, you would write something like: (+ 2 2). They mean the same thing in two different languages.

same applies for function calls, almost all languages use postfix notation for function calls. You could possibly write: myFunc(1, 2), but remember that no postfix notation is used, this includes the call expression, so, in Charon: (myFunc 1 2).

One exception is the member access expressions, which come in infix notation. You are allowed to access unbounded elements with this::expression::of::here, and bounded with that:other-expression. This will be cleared later on.

Functions

Functions in charon can be defined in four ways:

  • As pure package functions (defn).
  • As impure package functions (defn!).
  • As pure anonymous functions (fn).
  • As impure anonymous functions (fn!).

Function Purity

In charon, we can distinguish by two main types of functions: Pure and Impure. Pure functions cannot invoke impure ones, but impure can invoke pure ones.

Pure functions are expected to not have side effects. This makes them easier to test, predictable and able to be memoized.

However, due to the idea of easy interoperability, and the fact that runs close to Lua, most of the time you will have to manipulate the state, or call outside functions which are impure (Due to the fact that you can't ensure purity).

Strings

Like in Lua and other languages, strings can be delimited by the double quote character ". In Charon, the single quote character ' is used as an identifier and other constructs, so a single-quoted string is invalid.

This is a proposed model, the language does not support interpolation yet.

Also, strings will support new lines, and interpolation by using the dollar symbol $var-name or $(some "expression").

Concatenation of many strings is also possible with the str function, which takes all the arguments and joins them into a single string. If the argument is not a string, it will attempt to convert it to string.

Lists

List is the basic sequential structure in Charon, and can be defined though the list object literal [ ].

As lists are heterogeneous in type, and immutable, they can be used as a Tuple type.

To extract the nth item of a list, you can use the tuple access syntax, or the list access syntax:

Tables

Tables in Charon are what you might now as Maps or Dictionaries in other programming languages.

Not to be confused with the Lua's notion of table, which is a similar structure (In the sense that they're both hash tables). We diferentiate them in Charon by calling those Objects .

Creating a table is done through the table literal { }, which always should contain pairs of key-value elements. In charon, is very common the use of Symbols as keys, but you can use whatever type you want. As Lists, they're heterogeneous.

Lacking an even number of entries will result in a compile error.

Atoms

Atoms are the almost only way of persisting a state in charon. Other techniques that obscure data mutations to the program are heavily discouraged.

An atom is an object that has a mutable state, that you can read and write to.

All atom related functions, besides the atom creation itself, are impure, because they involve hidden state manipulation.

Also, atoms can contain any kind of datatype at any time.

Objects

As mentioned in the Table section, when we talk about Objects in Charon, we refer to unbound datatypes, with no data methods.

Note: There is a planned notion of object or "record" in Charon, but the proposal is not stable yet.

To put it in plain: Objects are any Lua table that is not a known data type, like Atoms, Lists or Tables. This exists in order to make the program able to define foreign objects used by an external Lua API. This also means that all object related functions are impure.

Comments

The only way to make a comment in Charon is by using the ; character. There are no multiline comments.

Lexical Scoping

There are two ways of declare values: By (let [] ...) and by (def ...). The first binds a local value, while the second a global (And exported) one.

Let Binding

The let expression binds to a local scope a set of values passed to the binding array. The array always must contain pairs of value-names and expressions representing those values.

Let, also allows destructuring a List object, by binding directly it's terms without having to write access by hand:

Def Binding

Def bindings are also called global bindings, although they do not declare the variable global for the runtime, they are global in a module level.

Those are public (Or "Exported"), and importing modules can read them. You cannot declare a global more than once, doing so will result in an error.

If, else and when expressions

In Charon, an if condition is an expression. This means that can be used for conditional assignment, as the result of evaluating the if will be either branch.

Also the first expression of the (if ...) construct is the then part, while the rest the else. In the else branch, only the last expression will be returned, although all are evaluated.

The syntax would be (if boolean then-expr else-1 else-2 ... else-n).

But aside from a simple if, you can make more complex branching without nesting if expressions.

when, like if is also an expression, and the same rules apply. The syntax is roughly (when value case-1 expr-1 ... case-n expr-n _ default-expr).

Note that all cases come in pairs. The else or default case in a when expression is noted with the _ symbol.

Loops and recursion

In Charon, we assume that you eventually will need some looping for side effects. That's because a for expression does not return anything, so the only way you really want to use this is for side effects, which include our beloved println! function.

The subject of adding a while loop is being discussed in the issue #15 .

Other means of more pure looping is the recursion. You could just write a tail-recursive function by calling the same function over again.

This sadly does not work easy with anonymous functions, but for that comes the self-recursion trait in to rescue, with #'.

We claim that the use of for loops is tightly related with side effects because it cannot produce any result without them.

For looping procedures that can produce pure results, use methods like list/map or list/reduce.

Operators

Charon has an equivalent for most binary operatos, which include the typical arithmetic operators like +, -, * and /, plus power operator **, modulo % and integer division //.

Also boolean operators for not (Only accepts one argument!), and, or, xor, nand and nor.

Comparison can be done though =, <> (not equal), >, <, <= and >=

Existential checks and null coalescence

In Charon you can pass non-boolean values to the if expression, but that can not be used to check for existent or not existent values, as unit will translate to true but nothing to false.

To check if a value is neither unit or nothing you check with the function (some? ...). If for some reason you need to distinguish between unit and nothing, you can do so with (nothing? ...) and (unit? ...) functions.

Aside, you can use the (or? value? default) function which takes a value, and returns it if was something, or returns the second argument if was nothing or unit. This function is also known as the coalesce operator.

Field access idiom

Sometimes, you need to manipulate external Lua objects, like in case of native or 3rd party libraries. For those cases, chaining function binding and object/get calls gets really tedious quickly.

For that reason, there is a special form that uses the :: and : symbols as infix operators in names.

The difference between those two, is that the first :: is what we call an unbound access, which in case of a field, is just a reference. But in case of function calls, the function is called statically.

The second, :, is used only in bound function calls, when we need to pass the self argument implicitly. Attempting to use this for referncing a field will result in a syntax error.

Try, catch and throw

Error handling in Charon uses the try-catch structure. However, catching is untyped an you might only have one catch per try. throw can throw any value and it will be handled by the nearest try.

Modules

Charon has a notion of modules, where each file represents one. Modules declare public fields that can be reused in other modules, via import or module declaration.

Wether you need to declare the module name explicitly or not depends on your project needs, take in mind that charon uses the file name if no module name is provided explicitly. Although is recommended that module and file name matches, sometimes you'll need to override that.

Resources