SCL - Simple Callback Language
The only system requirements should be a modern C++ compiler as I've only used things in the standard library.
$ mkdir build $ cd build $ cmake .. $ make -j `nproc`
This will give you a binary which you can do
./scl help for documentation on how to use. The commands are as follows:
build: generate a bytecode file for given source code
exec: run a bytecode file
eval: compile and run given source code
minify: Minify input source code
debug: generate bytecode text for given source code Notice that all of these commands take one argument as their input file.
- Statements end with semicolons (
- Automatic Semicolon Insertion: semicolons are optional, but can help to add meaning
- Not whitespace dependent
/* ... */
Builtin Global Variables
All of these values are reassignable and can be referenced and called within other scopes.
i: command line arguments (Note: only at global scope, see closures section)
o: leave current scope with return value provided as argument (this is known as the return operator in most other languages)
input: read values from terminal as a string (ie:
age = Num(input()))
if: performs functionality of ternary and branching
Str: converts given value to a string representation
Num: Parses a number (output is either Int or Float)
vars: debugging tool
async: run closure in async context (see section)
import: Load a native function or module
size: Gives size of given value, equivalent to
copy: Deep-copies given value
let name = "John Smith"; let age = 30, vehicle; vehicle = "Hot rod";
You can define macros that expand to larger expressions
Supports any valid JSON data
||Holds character sequences|
||Holds whole numbers (64 bits)|
||64bit floating point numbers|
||first-class functions, alternatives to blocks|
||Hold series of values|
||Dictionary with strings as keys|
Closures are first class functions but more important here as they're used to replace code blocks.
Defining a Closure
- Closure literals are enclosed in
- Variables can reference macros just like any other data, however code cannot modify their internals
let say_hello = (: print("hello, " + i); ); print("what's your name?"); let name = input(); say_hello(name); // greets user
Input and Output
Input is accessible via the local variable
i. Use the local variable
o to return a value. Although you can use i and o themselves, declaring variables (
let) or aliases (
using) for them can improve clarity and is required when they get shadowed by a previous scope.
let greeting = (: let name = i; let return = o; return("Hello, " + name); ); print(greeting(input()))
Everything is a Closure
Because of the increased tools for control flow, in this langauge, everything is a closure. Where other langauges use operators like
await, etc, this language can just use closures (often even with user-level implementations). Further, this langauge doesn't have blocks (usually in curly braces), because closures can serve the same purpose as them.
What does this mean?
To emphasize this point further, it can be thought that program files are wrapped in
) by the compiler. Command line arguments are passed as the closure's input (
i) and it's output (
o) is eqivalent to
sys.exit. So a simple echo program can be written as such
// Echo command line arguments print(i) // Exit success o(0)
This simplicity also applies to modules. There's no reason to have special operator for exports.
These are currently defined as builtins/standard library functions, but in the future they might be converted to operators.
if is just a function.
- Note: comma separated arguments implicitly converted to list
let gpa = Num(input()); // 3.86 if (gpa > 4 || gpa < 0, (: print("seems rigged"); ), gpa >= 2, (: print("PASS"); ), (: print("FAIL"); )); // PASS
You could also implement a less useful
if like so
let tern = (: i[1 + Int(i != 0)] ); let if = (: tern(i)() );
Pretty standard apart from it not being an operator.
let n = 0; while ((: n < 5 ), (: n += 1; print(n); ));
You can implement
while on your own like shown below. This will be required until conditional jumps get added to the VM bytecode.
let while = (: let args = i, break = o if (args(break), (: args(break) while(args) )) )
o()from the body will skip to next cycle
i()from body or condition will break out of the loop
truewhen you break out
Range Based For
The following definitions (among others) will eventually be included in the standard library. In the near future I'll add
range with similar functionality to python's version but without iterators.
let foreach = (: let list = i, action = i let index = 0, end = size(list) while((: index < end), (: action(list[index], index, i) index = index + 1 )) ) let map = (: let list = i, fn = i list = copy(i) let ret = foreach(i, (: list[i] = fn(i) )) if(ret == empty, list, ret) )
async in order to provide equivalent functionality.
Running code in a new thread
Lets walk through an exmaple that gets main points across. Imagine we have a function
request that takes a url and fetches it's content over the internet.
let request = import('request.so')
We can call request like any normal function and as we're awaiting the results, the VM can work on other tasks
let text = request('http://x.com') print(text); // x
However we can also perform the function call in a separate thread! We first make an
async wrapper for the
request function and then call it, receiving an eventual. Which we can call later to get the results.
// Alternatively we can make the request in a new thread let eventual = async(request)('http://x.com') // So that we can do other things while we wait on the download print('waiting...') // And then we can simply invoke the eventual to get the same behavior as before print(eventual()) // x
See async demo to see how easy it is to convert between functions that return promises and functions with callbacks
By default functions will implicitly return when they reach the end, however this behavior can be overridden by changing the value of
- This is dangerous because the thread won't return unless you already passed
oto something that can explicitly call it.
- Feature may be removed in future implementation and is usually wrong to use
- Unclear operators should be avoided, use functions instead
- ie -
export, etc. don't exist here
- ie -
- No redundant language features
- Avoid strong opinions
More coming soon
Most of these features are at least working. There are some things that are implemented haven't made their way into this guide and even more that I haven't implemented but have planned. If there's anything you want to see added, lmk.