Skip to content

WTF?

What would a language be without some fun? You’re welcome.

Locally scoped, var is more congruent with const than let is. I suppose we could’ve gone with let and make? Nah.

I’ve never liked the look of all the braces and else/if verbiage of an if statement. Why not get rid of them whenever possible?

I took some inspiration from ternaries and landed on this:

if (x > 10)
print("high")
: (x > 5)
print("normal")
: print("low")
if (y == 10) print("perfect!") : (y <= 6) print("danger zone") : print("not bad!")

Damn that’s beautiful.

However, there’s a catch. The parser expects only one statement per block. If any of them have more, we just wrap everything with braces. So, at most, you’ll only need two.

if {
(x > 10)
print("high")
: (x > 5)
print("medium")
:
print("low")
print("so be it")
}

This is necessary so the parser knows where the if statement ends and where the next statement begins. Otherwise, is print("so be it") part of the “else” block or just the next statement after if? Indentation has no meaning in ngn.

So, we sacrifice occassional weirdness for concise, powerful inline syntax.

The slice() method on strings and arrays mutates the original. Why? Because ngn follows the thinking that if you take a slice of something, it’s, well, gone from the thing. That’s how pizza works, right? And I love pizza.

If you don’t want to mutate the original, use copy() - it has the same API as slice().

Functions are isolated data environments; i.e. you can’t reference outside var or const data, only pass them in. However, you can reference/call sibling functions and globals - literally data defined with global and other top-level things (imports, enums, models, functions, etc).

You know how in Rust you use &, as in (name: &str), to borrow data for a function, otherwise you transfer ownership of the data to the function? ngn essentially does the opposite - function params are “borrowed” by default (they get a non-mutable copy).

This means that if you want to mutate a passed-in var within a function, you need to mark the param as “owned”. Optionally, you could also do this if you’re not going to use the data after the function runs; which gets you a smidge of early memory cleanup.

var x = "hello"
// the `<` in front of the type assumes ownership
// and enables mutation inside the function
fn doSomething(stuff: <string) {
// read and/or mutate "stuff".
// memory is automatically cleaned up
}
doSomething(x) ✅ // moves ownership of `x` to the function
print(x) ❌ // `x` is no longer available, since it's ownership was moved

In contrast, you can continue using passed borrowed data, but it can’t be mutated inside the function.

var x = "hello"
fn readThing(thing: string) {
// can read "thing", but not mutate.
// the copy's memory is automatically cleaned up
}
readThing(x) ✅ // does not move ownership of `x` to the function
x = "goodbye"// `x` is still available

ngn doesn’t have a null value. Instead, we have a Maybe enum with Value<T> and Null variants. There are various ways of handling this, to get access to the data.

match (maybe_value) {
Ok(val) => print(val),
Null => print("no value")
}

If there’s a value, it’s unwrapped and assigned.

const thing = maybe_value ?? "fallback"

Unwrap the value and make it available in the block.

if (maybe_value?) {
// success scenario
// "maybe_value" is the actual data here
}
// note that "maybe_value" is still a "maybe" here, not the actual data

If it’s a Maybe::Null, run the failure block. You must either return or break within the block.

check maybe_value? {
// failure scenario
// must return or break
}
// "maybe_value" is the actual data here

check can handle Result<T, E> as well. Access the error value, within the block: check value?, err? {}

This unwraps the Maybe for further testing of nested values; however, the assigned value is wrapped in Maybe.

const maybe_location = user?.meta_data?.location
// still need to unwrap maybe_location, because it could be `Maybe::Null`

The json.parse() method returns a Maybe for raw objects and unknown data. So, you need to check for Maybe using one of the above methods. However, this unknown data (from a user, for example), could throw a runtime error if you start using dot notation to access fields.

const maybe_data = json.parse(data)
const name = maybe_data?.name // runtime error if "name" isn't there

You may want to destructure the data, which returns Maybe for each field.

const { name } = maybe_data
check name? {
// any error handling
return
}
// "name" is actual data here

Yeah, it’s not called that in ngn, but you can use the phrase if you’d like. We call it the toolbox; after all, anyone who works on an engine has a toolbox, right?

import { ceil } from "tbx::math"

idk, there’s just something about having std in code that’s a bit cringe for me. Perhaps I’m just nitpicking.



If you made it this far without puking, you just might fall in love with ngn. If you’re still skeptical, at least checkout our Sick Picks.