Charm

From Esolang
Jump to navigation Jump to search

Charm is a mostly concatenative, stack-based functional language. It operates on the property that all code is a list of data, and that all data can be interpreted as code (very much in the Lisp spirit). It was created in 2017 and was actively maintained by User:Aearnus until early 2019. It is highly recommended that you read the Readme in order to get the newest, highest quality information. What's written here may not be fully updated or as far in depth as what's written in the Readme.

Quick Links

Discord (Chat with the developers and other Charm programmers!): https://discord.gg/RQu5adW

Github (Download Charm! Read the README!): https://github.com/Aearnus/charm

Glossary (Browse the functions!): https://aearnus.github.io/charm/

The Charm Philosophy (Why Charm?)

  • Lispiness: All code is data and all data is code. With this comes very powerful tooling for metaprogramming and surprising abstractions.
  • Functionality: Everything written is a function -- with the exclusion of the next point.
  • Simplicity: There is little syntactic cruft. The only significant syntax in Charm is the space character, :=, ", and [ ]
  • Speed: Speed takes a forefront in the Charm Interpreter, with stack manipulation code running at near native speed for comparable operations. Additionally, any truly performance critical sections of your code can be written using the C++ FFI and run directly inside of Charm.

Example Charm Session

This was taken off the Charm Readme with minimal modifications.

aearnus@aearnus:~/scripts/charm$ ./charm
Charm Interpreter v0.0.1
Made by @Aearnus
Looking for Prelude.charm...
Prelude.charm loaded.

Charm$ addOneToN := " n " getref 1 + " n " flip setref
Charm$ putN := " n " getref put pop
Charm$ putN
0
Charm$ addOneToN putN
1
Charm$ [ addOneToN ] 10 repeat
Charm$ put
[ addOneToN addOneToN addOneToN addOneToN addOneToN addOneToN addOneToN addOneToN addOneToN addOneToN ]
Charm$ i
Charm$ putN
11


About Charm

Basic Syntax

Charm has an extremely simple syntax. Everything is space delimited, and there is only one special construct - the function definition. Functions are defined using function name := function body and are tail call optimized for most use cases. Lists are defined using [ ]. Strings are defined using " ". Numbers can be either integers (long longs) or floats (long doubles). The stack is initialized to 20000 zero integers.

Everything in Charm is a function - the four types are number functions, string functions, list functions, and defined functions. Functions always get executed immediately upon being called -- but the first three types simply push themselves to the stack.

Implementation

Many functions are preprogrammed (in C++) into Charm. This includes object and stack manipulation, arithmetic, and some combinators. But, others are in the standard library of Charm, called Prelude.charm. The glossary explains functions with its arguments using calling order, placing the deepest value on the stack first. This mirrors how it would be written with Charm itself.

Optimization

Charm uses a self-written optimizing interpreter. I'm very interested in the use cases and the effectiveness of the optimizations. The interpreter performs two optimizations: inlining and tail-call.

Inlining optimization is enabled by default through the compilation option -DOPTIMIZE_INLINE=true. Inlining optimization occurs if the interpreter detects that a function isn't recursive. If it isn't, the interpreter writes in the contents of the function wherever it is called, instead of writing the function itself (like a text macro). This removes 1 (or more, depending on how deep the inlining goes) layer of function redirection.

Tail-call optimization is necessary for this language, as there are no other ways to achieve a looping construct but recursion. There are a few cases which get tail-call optimized into a loop. These few cases are:

f := <code> f
f := [ <cond> ] [ <code> f ] [ <code> ] ifthen
f := [ <cond> ] [ <code> ] [ <code> f ] ifthen
f := [ <cond> ] [ <code> f ] [ <code> f ] ifthen (gets unrolled into a loop of the first form, ends up looking like f := [ <cond> ] [ <code> ] [ <code> ] ifthen f)

(If you can think of any other cases or a more general case, please open an issue!). These optimizations should allow for looping code that does not smash the calling stack and significant speedups. If there are any cases where these optimizations seem to be causing incorrect side effects, please create an issue or get into contact with me.