Yet another JavaScript-like language with a Pythonic syntax.

Saga is a new programing language designed to replace JavaScript, getting rid of its weird syntax or runtime behavior, replacing it with a host of new syntax and powerful features. Designed to be easy to write and still easy to comprehend, Saga allows you to write expressive, type-safe and performant code devoid of repetitive boilerplate.

With Saga, you can leverage the full power of JavaScript in a robust and strongly-typed language without the fear of type-related errors or bugs, all coded in JavaScript. Saga also adds many features and improvements to JavaScript to assist in functional, object-oriented, declarative, imperative and meta-programming.

#: Generates a custom Fibonacci sequence
#: with an arbitrary set of integers
rec gen def fib[A: num](start: []A, term: A): A =
  if term in keyof start:
    yield start[term - 1]
  elif term > len start:
    yield from let x in term - len start to term
      select fib start x
      fold left (+)
    raise new Error \Invalid\ sequence


The web is a platform with a mostly fixed set of APIs and web technologies, including HTML, CSS and JavaScript. JavaScript was not initially designed to be scalable or a good viable option for building complex applications for the web, and as such, new languages and transpilers for existing languages have been developed. And with the advent of new technologies such as WebAssembly, web applications not written with JavaScript can perform much faster than with JavaScript.

Saga started out as a holiday project in experimenting with language grammars, writing and experimenting with code snippets, researching and writing documentation. Saga's influences are diverse, starting out with Python, Ruby, OCaml, Flix, Rust, Bash, C#, F#, Haskell, Nim, Elixir, Scala, YAML, Stylus and far too many languages to list.

Saga's Goals

The golden rule is, everything in Saga is JavaScript, which includes the compiler, libraries and tooling, so there's no need to install special software other than Node.JS that would oftentimes fail to work. Saga code compiles one-to-one into the equivalent JS, and there is no interpretation at runtime.

The compiler is performance-optimized so that it can scale to any codebase size, producing performant and readable JavaScript (and it's getting faster). And because of that, you can use any existing JavaScript library seamlessly from CoffeeScript (and vice-versa).


Saga will have a standard library covering many domains all the way from primitive operations to file manipulation and heavy number crunching, solely from the NPM community. There is even a module to convert existing JavaScript or TypeScript code over to Saga, with tons of options available.

Saga will compile not only to JavaScript but also directly to WebAssembly, making it one of the few (or the only?) language to ever compile to either language, without the need for any form of conversion.

This document is currently in the works and is my largest project to date. Some things are going to change. I will be posting a Trello on Saga very, very soon, for all of you to see.

Feel free to open or contribute to the project on this GitHub repository:

Version Name Lists

  • 1.0 Don Quicksort
  • 2.0 Lord of Recursion
  • 3.0 Pride and Processes
  • 4.0 Frankenstack
  • 5.0 Moby Docker
  • 6.0 Gulliver's Transpiler
  • 7.0 Parameters Lost
  • 8.0 Wizard of OOP

Version 1.0

  • Grammar (rework)
  • Documentation (language and API)
  • Language reference
  • Lexer and parser
  • Trans-compiler
  • Tooling (VSCode, Atom and Nova)
  • Theme, branding and website


  • JavaScript with Babel: compiler and standard library
  • Lodash: Core libraries

Language Ideas

This reference is structured so that it can be read from top to bottom. Later sections use ideas and syntax previously introduced. Familiarity with JavaScript is assumed.
The file extension for Saga is .sa or .saga.

Saga uses significant whitespace to delimit blocks of code. Semicolons ;, even commas , are not needed to terminate or separate expressions, ending the line would do just as well. Curly braces {} to surround blocks of code are entirely optional, though it is preferred you use indentation.

You can omit the parentheses and commas when invoking a function. The implicit call wraps until the next infix operator or until the end of the line.

x y z == x(y, z)
x.y z == x.y(z)
v.w x.y z  == v.w(x.y, z)
v.w(x.y z) == v.w(x.y(z))

Line comments begin with # or #: and continue up to the end of the line. Block comments begin with either #{ or #[] and end in }# or ]#. Note that #: and #{}# are considered tokens in the language, and are used to generate documentation.

Special comments such as #!, #= and #? are used to tell the compiler what and where to compile, and what modules to require. Some are reserved for software tooling, such as identifying issues or important things.

#: @author John Doe <[email protected]>

#! usr/bin/env wasm
#= x = 2
#? TODO fix grammar highlighting for some parts

Inline comments begin with #( and end in ).and can be inserted wherever needs be


Every variable should be declared before you can use them. This lets you and the compiler know which variables should be overshadowed and which ones can be reassigned, to prevent variable or reference-related errors from occurring.

All variables are block-scoped. This makes sure that the values do not leak out into the parent scope. Declarations can be scoped with do blocks or any control flow statement.

  let x = 10 # creates a local variable named x
print x #! RefError: variable `x` is not defined

The value of the last line of a scope is implicitly returned.

val greet = if displayGreeting:
  let message = "Enjoying the docs so far?"
greet #= 'Enjoying the docs so far?'

There are four ways to declare variables: var, val, let and const. val and const declarations are "immutable", aka "cannot change", and to optimize compilation performance, we recommend you use val and const rather than their immutable counterparts.

var x = 5   # mutable, re-declarable
val x = 5   # immutable, re-declarable
let x = 5   # mutable, not re-declarable
const x = 5 # immutable, not re-declarable

"Re-declarable" means that on the same scope" reusing the same let binding name overshadows the previous bindings with the same name. So you can write this too:

let x = 10
let x = 10 #! RefError: x already declared

  let x = 5
x #= 10

  x = 2
x #= 2

Everything is an expression

Almost everything is an expression, which means you can do things like:

let x = if 2 + 2 == 4: 10 else: 0
x #= 10

Things such as loops, switch statements, and even try/catch statements are all expressions.

If you want to simply declare a variable and not initialize it, you can.

var x


Booleans, Void, Nil

Aliases as in CoffeeScript.

true == yes == on
false == no == off

Nil, none and null are synonyms. Void and undef are too.

nil == none == null
void == undef


Saga has support for integer and floating-point literals, in multiple bases: 2, 4, 6, 8, 10, 12 and 16. All floating-point numbers are distinguished by a dot, or the modifiers r, p and/or s in any combination.

val base04 = 0b100 #= 4
val base04 = 0q100 #= 16
val base06 = 0s100 #= 36
val base08 = 0o100 #= 64
var base10 =   100 #= 100
var base12 = 0z100 #= 144
var base16 = 0x100 #= 256

Integer literals without a type suffix are automatically cast into the supported range, by default. Integers can be fast cast There is no limit for the length of integer literals apart from what can be stored in available memory.

Numeric literals are case-insensitive, and can contain leading 0s or underscores. A prefix cannot be immediately followed by an underscore, and a numeric literal cannot end in an underscore.

7     2147483647                        0o177    0b100110111
3     79228162514264337593543950336     0o377    0xdeadbeef
      100_000_000_000                   0b1110_0101

In floating-point literals, repeating digits are delimited with r, p controls the exponent and s controls the precision after the decimal point, in that order.

0x0.1r3p2s6 == ((1 / 16 + 1 / 40) * 2 ** 16).fix 6

Numbers can be automatically followed by a type suffix, such as i32, f64 or any of the like. Un-suffixed literals are by default i32 for integers, and f64 for floats.

123                              # type i32
123i32                           # type i32
123u32                           # type u32
123:u32                          # type u32
let a: u64 = 123                 # type u64
0xff                             # type i32
0xff:u8                          # type u8
0o70                             # type i32
0o70:i16                         # type i16
0b1111_1111_1001_0000            # type i32
0b1111_1111_1001_0000i64         # type i64
9007199254740991                 # type i64
9007199254740991u                # type u64
0b0_1                            # type i32
0u                               # type u32

Examples of invalid integer literals:

#! leading or trailing underscores after numeric parts

#! undefined is not defined

#! uses numbers of the wrong base

#! integers too big for their type (they overflow)

#! prefixed-base literals must have at least one digit

Arbitrary-radix literals start with the base first, then a combination of alphanumeric characters, then Þ and þ, or by decimal digits separated by commas.

1r3 == 40 ** 3

Defining functions

Defining functions is very lightweight in Saga.

=> # empty function
x = x => y #

As you see, function definitions are considerably shorter! You may also have noticed that we have omitted return. In Saga, almost everything is an expression and the last statement is automatically returned.

You can still use return to force returns if you want. Alternatively, mark the function with a / to suppress automatic returns.


JavaScript Saga
Enforced by linter None needed


JavaScript Saga
// line comment # line
/* block comment */ #[ block ]#
/** doc block comment */ #{ doc-block }#
#: doc-line
#! shebang
#? bugfix
#( inline )
#_ playground


JavaScript Saga
const x = 5 Same
var x = 5 Same
let x = 5; x += 1 Same

In addition, val behaves like const but can be redeclared like var.

Data Types

Like JavaScript and Python, there is no char type.

Type Default Value Description JavaScript equivalent (class)
nil nil The constant nil undefined
bool false A boolean value Boolean
int 0 32-bit integer Number
float 0. 64-bit floating point Number
str '' "" String String
regex `` Regular expression RegExp
func => Function Function
seq () Generator sequence Generator
bits bits'' Bit stream Buffer
list [] Ordered list Array
set set[] Set Set
map {} Hash map or dictionary Object, Map


JavaScript Saga
"Hello world!" Same
'Hello world!' Same
"hello " + "world" hello" + "world"
'hello'.repeat(3) hello" * 3
`hello ${message}` `hello $message`
\u03B1 inside " \h{alpha}
${msg.toUpperCase()} $msg:su
'hello'[1] Same
'hello'['hello'.length - 1] 'hello'[-1]
'hello'.slice(3, 4) 'hello'[3:4]
/x/.test('next') 'x' in 'next'
(/x/) in 'next'
'hello'.replace('l', 'r') 'hello' =< `l`r`
[...hello].length len 'hello'
'hello'.length size 'hello'
chalk`{blue hello world}` Same


JavaScript Saga
null, undefined nil, null, none, ()
true, false same; plus yes/on, no/off
!, &&, || same, plus not, and, or
!x != !y x ^^ y , x xor y
x && y (short-circuit) x !: y
x || y (short-circuit) x ?: y
a ?? b Same
a == null ? a : b a !! b
===, !== ===, !== (Referential)
==, != (Structural)
==, != =~, !~
<, >, <=, >= Same, but no type coercion
a < b ? -1 : a > b ? 1 : 0
a.localeCompare(b) (strings)
a <=> b


JavaScript Saga
1, 0x10, 0o40, 0b10_10 Same
1e40 Same
13.1875 Same
No complex number support 1j
144, 36 0z100, 0s100
Infinity, NaN inf, nan
No fraction support 1 / 3r, 0.r3
+, -, *, /, % Same
1 / 4 | 0 1 ~/ 4
((1 % 4) + 4) % 4 1 %% 4
Math.max(3, 4); Math.min(3, 4) 3 *> 4; 3 <* 4;
&, |, ^, ~ same
>>, <<, >>> same; no >>>
x++; x--; ++x; --x x += 1; x -= 1;
1 >>> -20 1 <<< 20
[...Array(100).keys()] ..100
[...Array(102).keys()].slice(1) 1..=100

Lists, Sets and Maps

Saga's JavaScript runtime uses Immutable.JS for its internal data structures.

x.=push arr
JavaScript Saga
[1, 2, 3] Same
[1, 2, 3].concat([4]) [1, 2, 3] + 4
Array(3).fill([1, 2, 3]).flat(1) [1, 2, 3] * 3
[1, 2, 3].filter(x => x === 1) [1, 2, 3] .filter (== 1)
arr.includes(ele) ele in arr
!arr.includes(ele) ele !in arr
var [x, y] = [1, 2] Same
[...x, ...y] [*x, *y]
[...x, ...y] [*x, *y]
JavaScript Saga
new Set([1, 2, 3]) {1, 2, 3}
new Set('hello') {*'hello'}
new Set('hello').has('h') 'h' in {*'hello'}
Symmetric difference
Superset, subset >=, <=
Strict superset, subset >, <
JavaScript Saga
{} {:} (mandatory colon)
{a: 1, b: 2, c: 3} Same
map?.prop; map?.method() Same
map.prop = 10 map.prop set 10 or .= 10 returns new map; otherwise same
'prop' in map 'prop' of map
!('prop' in map) 'prop' !of map
delete map.prop del map.prop returns new map
map.prop map!.prop would throw if it does not exist
{...details, prop, let: 2} {*details, :prop, let: 2}
{...details, let: 2} details | {let: 2}
Object.keys({}) keyof {} (Same for values and entries)
map.y = 40; map.x() map.y = 40; ~.x()


fn => 1       # anonymous function (fn keyword optional)
x => 1        # function with one parameter
(x, y) => 1   # function with two parameters

fn x(y) = 1       # named function
fn x(&y: 1) = 1      # named parameter
fn x(&?y) = 1     # optional parameter
fn x(*y) = 1      # variable arguments
fn x(y = 1) = 1   # default parameter
fn x(y: int): int = 1 # with type annotations

Compound Expressions

Everything is an expression!

var integer = alias int | byte | short | nint | long
var result = if a then 'hello' else 'bye'
var file = match
  when x is int -> 1
  else -> 0
JavaScript Saga
a ? b : c Same
if () Same (no brackets needed)
if (!expr) unless expr
else if elif
for (var i = 1; i <= 10; i++) for (var i in 1 .. 10)
for (var i = 1; i < 10; i++) for (var i in 1 ..= 10)
for (var i of map)
for (var i in map)
in and of are swapped
switch Same, explicit fallthrough + go-to
try Same
throw, catch raise, rescue
break, continue halt, skip
(deprecated) with fs.readFile() as (let file) {}
while (true) {} repeat {}
while (x < 10) { x++ } Same
while (x != 10) { x++ } until x == 10 { x += 1 }
do { x++ } while (x < 10) repeat while x < 10 { x += 1 }
do { x++ } while (x != 10) repeat until x == 10 { x += 1 }


Saga is/has (a):

  • Familiar syntax that resembles many languages including Python, Ruby, Elixir, OCaml, etc, hence a low learning curve
  • Statically typed by default, though with optional dynamic and optional typing
  • Compiled to various backends and languages on virtually whichever platform you see fit
  • Multi-paradigm; combining object-oriented, functional, declarative, meta- and procedural programming in one


SagaML is a compact and complete plain text markup language that blends the simplicity and readability of Markdown and Textile with the flexible nature of embedded templating languages. SagaML allows you to focus more time on your content and write reusable web components, stylings and libraries in an easy and modular way, while generating powerful HTML, CSS and JS.

GitHub - nxltm/saga: A hackable programming language I’m working on. Currently WIP.
A hackable programming language I’m working on. Currently WIP. - GitHub - nxltm/saga: A hackable programming language I’m working on. Currently WIP.