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
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 commandcharon
, which can compile .crn
files into
.lua
files. At this moment, no REPL is available to test
charon directly. charon
command options are:
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.