nguigen, ngg for short, is a general purpose programming language for developing software that is (reasonably) portable, performant, and safe. It compiles to multiple high-level languages including C and JavaScript. ngg itself is written in ngg, which is then built by compiling to C first.
ngg is still evolving, making it hard to describe its exact nature and scope. It originated as a simple script to convert the same GUI spec to C/GTK, C++/Qt, and PyGI code. But this spec-based approach was too limiting and cumbersome, always needing the support of a real programming language. Combined with my taste for low-level programming and frustration with the error-prone nature of C, ngg started becoming a real programming language on its own. At this point, it appears to be the following:
A safer and ergonomic way to develop systems software and GTK-based desktop applications (status: mature enough; examples include the ngg compiler, Vara, and Amsam)
A way to write code that will run on multiple platforms natively, without needing anything like Electron (status: proof-of-concept stage)
A way to create portable wrappers for libraries (status: usable; this is how ngg currently wraps GTK and HTML5 with the same interface)
A friendlier alternative to Rust if you don’t want that kind of guarantees but still want it to be safer than C
ngg has the following features:
Procedural, object-oriented, and reactive paradigms
Features to reuse existing libraries from other languages
Semi-automatic memory management with unique, strong, and weak ownership. You can avoid many errors like use-after-free and double-free with these features (but the language and the compiler aren’t smart enough to prevent you from making mistakes yet).
Immortal classes whose objects are never destroyed, which help prevent issues like use-after-free without needing complex ownersip tracking.
Designed to reduce overhead by default; for example, all classes
and methods are final unless declared to be nonfinal so the
unnecessary overhead for dynamic dispatch support won’t be
there
No surprises like this one in C++: if not declared
virtual, you might end up calling the incorrect method
implementation in the context of inheritance.
Metaprogamming support: you can write code that expands during compilation. This includes templates, conditional compilation, and things like stringifying and enum.
Concepts like shadow and flenum that
are not rocket science, but new.
Type inference in declarations with initialization
One day this document might become a book, a reference manual, or even a formal spec, but as of now, it is just a collection of random notes, slightly organized whenever possible. I can assure you is that the language and the compiler are more mature than this document.
One thing that makes Rust attractive is that it gets rid of memory management pitfalls without employing a garbage collector. ngg is also being developed with this goal in mind. Other than that, Rust and ngg are totally different languages. For starters, Rust has object composition while ngg has traditional inheritance-based OOP. Another difference is that, ngg is arguably easier to write, and it maintains this easiness in both systems and application levels.
This document is strictly meant to be a quick reference for those who already know other programming languages, especially C, Java, and the like. It will elaborate on ngg features that differ from or improve on these languages, employing comparisons even within definitions. ngg is by all means okay as one’s first programming language, but this document is not the best starting point then.
ngg has a syntax that is strange but easy to pick up and use. We will explore this syntax in this chapter, but I want you to remember that ngg is not an alternative syntax to some existing language – it would still be a new language even if you fit the ngg compiler with a new front-end that accepts syntax similar to C or Java.
Expressions use prefix notation and keywords are used as
operators. There is no operator precedence. sum a mul b c
means a + b * c. sum 1 sum 2 3 means
1 + (2 + 3) while sum sum 1 2 3 means
(1 + 2) + 3. Parentheses can be used for
readability.
In languages with C-like syntax, blocks (like functions,
branches, loops, etc.) are wrapped inside braces ({}) while
in Python, a colon (:) opens a block and indentation
indicates nesting. In ngg, blocks are automatically opened but closed
explicitly with a semicolon (;).
Statement-ending semicolons are optional unless you write
multiple statements in a single line. For example, a branch that checks
a to execute b followed by c can
be written in a single line as if a b; c;;. Note that there
are two semicolons in the end – one to terminate the statement
c and one to terminate the block
if a.
Function calls have the following syntax:
=funname/[arg1, arg2, ...]. If the function takes no
arguments, it can be written as =funname/[], or simply,
=funname.
Backslash (\) is used instead of dot
(.) for member selection, and the components are written
bottom-up. So laptop1.battery.capacity in Java-like
languages would be capacity\battery\laptop1 in ngg. It
starts making sense when you read backslash as “of”.
ngg is strongly and statically typed. The compiler employs type inference to make programming easier (to be clear, this is Go-style type inference, not the one you would encounter in functional languages).
Basic types like int and double are present
in ngg which translate to C directly without any surprises.
long and unsigned become single-character
prefixes – for example, long long is written as
llong and unsigned int is written as
uint. Fixed-size integers are named i8,
u8, i16, u16, etc.
ngg has string for immutable strings (string constants)
and mstring for mutable strings. When the target language
is C, they become const char * and char *
respectively. Yes, this approach of representing strings as
NUL-terminated character arrays is not particularly safe, but it is a
requirement to interface with existing C libraries. A safer abstraction
can be expected in future for high-level usage.
Structs and classes are both custom-defined containers with any number of fields. However, they have some fundamental differences.
Structs are scalar while classes are heap-allocated dynamic objects. Optimizations could allocate class objects on the stack, but that will be transparent to the programmer.
If you have a class MyClass, the type
MyClass automatically translates to MyClass *
in C (similar to Java and Python).
Struct objects are copied during assignment while for class objects, it is the reference (pointer) that gets copied.
Classes can have methods while structs cannot.
Constructors and destructors are automatically invoked for classes.
Structs cannot own other objects. This is a safety feature to prevent double free and use after free considering struct objects (and thus the pointers they contain) can be freely duplicated.
It is a common idiom in systems programming to use an integer to hold a set of flags, where each bit represents a particular flag. To set and read these flags, C programmers define constants with powers of two as values and employ error-prone bit operations. ngg offers a feature called “flenum” (short for “flag enum”) to automate this entire business.
The compiler automatically converts flenum accesses to bitwise
operations. If a flenum Flags has an element
FLAG1 and flags is of type Flags,
accessing FLAG1\flags will result in some bitwise
operation(s) between flags and FLAG1\Flags.
For example, you can assign true or false to
FLAG1\flags, and the compiler will take care of generating
the bitwise operations. This makes dealing with binary files and
interfaces far more easier than in C.
// Nandakumar Edamana
// 2026-01-31
// @test-stdout 0-5-0-1-0-1-0-
flenum Flags
F1,
F2,
F3,
;
fun $main
var flags Flags
=printf/["%d-", flags]
==F1\flags true
==F3\flags true
=printf/["%d-", flags]
=printf/["%d-", F2\flags]
=printf/["%d-", F3\flags]
==F3\flags false
=printf/["%d-", F3\flags]
var bv1 / true
==F3\flags bv1
=printf/["%d-", F3\flags]
var bv2 / false
==F1\flags bv2
=printf/["%d-", F1\flags]
=printf/["\n"]
return 0
;
ngg is meant to be memory safe even when compiled to low-level targets like C. It tries to achieve this without having a garbage collector. Every dynamic object is automatically deleted when its owner (a scope like a function or another object) gets deleted. If the object is meant to outlive a scope, its ownership can be transferred, or reference counting can be used. While these features are already there and used heavily in projects like Vara, there are some gaps, and the compiler still lets one perform manual memory management. That wouldn’t be the case in future.
TODO Dynamic objects without any ownership
(var x new CLASS instead of local x new CLASS)
can be referred to by any other object anywhere, as they are never
freed. The type checker should prevent such references being copied to
pointers with ownership (to prevent use after free). This enforcement is
pending.
If a class is marked immortal, its objects are never
destroyed so that they can be safely referred to from anywhere in the
code without worrying about issues like use-after-free. This
simplification in ownership tracking comes at the cost of leaking
memory. However, it should be okay for one-shot programs, as the entire
memory is reclaimed by the OS when the program terminates. Immortal
classes should be okay in daemons (servers) as well, as long as only a
known number of instances are created (e.g.: an Application object or a
couple of workers which need to stay until the server is killed
anyway).
Subclasses of immortal classes should also be marked immortal.
As the name suggests, an object under unique ownership has a single
definite owner, which can be another object or a scope (like a function
or a branch). The owned object gets deleted during the deletion/exit of
the owner. Such ownership can be specified using the keyword
local in local variable declarations and the keyword
own in function parameter and class attribute
declarations.
TODO examples
Unique ownership can be transferred using the keyword
steal. For example, if a function has a local object
defined using local obj1 new MyClass, and it needs to be
passed to a function fun myfun takes obj own MyClass (which
takes over the ownership), the call is to be made like
=myfun/[steal obj1].
TODO
// Started 2026-06-27
// @test-clopt --x-rc-for-all-classes
// @test-stdout 13-13-13-13-
// TODO checks for the generated code
class Value takes n int;
class MyClass1 takes value ownrc Value
fun print =printf/['%d-', n\@value];;
;
fun process takes x ownrc Value
=printf/['%d-', n\@x]
;
fun $main
ownrc value newrc Value/[13]
own obj1 new MyClass1/[value]
own obj2 new MyClass1/[value]
=print\obj1
=print\obj2
=process/[value]
=printf/['%d-', n\@value\obj1]
=puts/['']
;
// Started 2026-06-28
// @test-clopt --x-rc-for-all-classes
// TODO FIXME `weakrc nullable` doesn't make sense; can I do `nullable weakrc`?
class Node takes parent weakrc nullable Node, child ownrc nullable Node;
fun $main
ownrc n0 newrc Node/[nil, nil]
ownrc n1 newrc Node/[n0, nil]
ownrc n2 newrc Node/[n1, nil]
==child\@n0 n1
==child\@n1 n2
;
TODO
// A linked list example that resembles how it is done in
// functional languages. If not for illustrating the use of
// `switch finally`, only one kind of class is needed to represent
// a node (end of the list will be marked by nil pointer).
//
// 2025-09-24
nonfinal class list
staticrtti;
;
class Nil extends list;
class cons extends list takes h int, t own list;
fun length gives int takes xs list
switch finally xs
case [Nil]
return 0;;
case as cons consobj
return sum 1 =length/[t\consobj];;
;
fun $main
local xs / new cons/[4, new cons/[5, new cons/[6, new Nil]]]
=printf/['%d-\n', =length/[xs]]
;
Abstract classes need to be marked explicitly using the
abstract keyword because the compiler cannot know if the
user simply forgot to implement an abstract method from a parent
class.
Abstract classes are automatically considered nonfinal.
Abstract methods are automatically considered nonfinal.
The compiler still generates code for abstract methods. So, an
assert(false) is included to avoid “control reaches end of
non-void function” errors from the C compiler.
TODO
// Nandakumar Edamana
// @test-guibuild
// @test-norun
include gui
class myapp
function $construct
wid wnd mainwindow given [ title "My Application" ]
wid vbox1 vbox
classwide
wid txt1 entry;
wid btn button given
[ label "Click me!", onclick onbtnclicked ]
;
classwide
wid lbl1 label;
;
;
;
lis onbtnclicked for click of button
local n1str / =ngg-entry-clone-text-utf8/[txt1]
local msg / f'Hello, {n1str}'
=set-label\lbl1\this/[msg]
;
;
fun $main
=gui-init
new myapp
=gui-run
;
Abstract classes need to be marked explicitly using the
abstract keyword because the compiler cannot know if the
user simply forgot to implement an abstract method from a parent
class.
Abstract classes are automatically considered nonfinal.
Abstract methods are automatically considered nonfinal.
The compiler still generates code for abstract methods. So, an
assert(false) is included to avoid “control reaches end of
non-void function” errors from the C compiler.
Reactive programming is a handy paradigm in which changes to certain variables automatically get propagated to parts that depend on those variables. Consider programming a Web app that lets one download some resources. When a download starts, a throbber, a progress bar, and a Cancel button need to become visible. Their status needs to be kept up-to-date as the download progresses. If done in the traditional way, the handler of the “download-progress” event (a cooked-up name) needs to know about all those parts where the status has to be reflected. In reactive programming, on the other hand, one declares the relationship that each component has with a variable the stores the status, and all the “download-progress” handler does is updating this variable.
// Nandakumar Edamana
// 2024-01-05
// @test-guibuild
// @test-oc-fgrep myapp_count_Observable_set(((myapp *) user_data)->count, ((myapp *) user_data)->count->value + 1);
// @test-norun
include gui;
include reactive;
const MAXCLICK / 4
class myapp
var $count int
function $construct
wid wnd mainwindow given [ title "Reactive Application" ]
wid vbox1 vbox
classwide wid lblA label given [ label xif (ge $count MAXCLICK) then 'Logged in as User 1' else 'Not logged in' ];
classwide wid btnA button given [ label 'Log Out', visible (ge $count MAXCLICK), onclick onlogout ];
classwide wid btn button given [ label "Click me!", onclick onbtnclicked, visible (lt $count MAXCLICK) ];
classwide wid lbl1 label given [ label (dats acat ['Clicked ', $count, ' time(s)']) ];
classwide wid txt1 entry given [ text (dats acat ['Clicked ', $count, ' time(s)']) ];
classwide wid prg progressbar given [ fraction (as double div $count as double MAXCLICK) ];
;
;
;
lis onbtnclicked for click of button
==$count sum $count 1
;
lis onlogout for click of button
==$count 0
;
;
function $main gives int
call gui-init;
new myapp;
call gui-run;
return 0;
;
Abstract classes need to be marked explicitly using the
abstract keyword because the compiler cannot know if the
user simply forgot to implement an abstract method from a parent
class.
Abstract classes are automatically considered nonfinal.
Abstract methods are automatically considered nonfinal.
The compiler still generates code for abstract methods. So, an
assert(false) is included to avoid “control reaches end of
non-void function” errors from the C compiler.
TODO
The purpose of cif, celif, and
celse is to enable conditional compilation. The kinds of
expressions that can be used with cif and
celif are extremely limited, but should serve most
purposes. The supported ones are boolean literals, integer literals,
and, or, not, streq,
compiletime variables, and compiletime function calls. Boolean and
integer literals are supported to facilitate quickly disabling portions
of code with cif false or cif 0 (like
#if false in C). streq _ngg_targ TARGET can be
used to conditionally compile a block of code for TARGET
exclusively. =_ngg_defined/[ID] will return true if ID is
defined during compiletime.
Careless use of _ngg_enum_to_array_of_string can easily
clutter the generated code. Hence the compiler will warn if it is
applied on the same enum more than once. The compiler will also prevent
its use in local contexts so that it won’t miss any duplicate
application inside a local context in an included file. The recommended
way to use _ngg_enum_to_array_of_string is to apply it on
an enum only once, store the returned array in a global variable, and
write some wrapper functions to make use of it, which can be called from
inside any file in your project.
This chapter dumps a collection of notes that have not been placed in any specific chapter.
The generated ngd file will contain the following in the case of C output:
--ngd-use-ngh is set, except for the first one)Regarding the .o target, yes, the recommended way to generate
Makefile rules for .o files is to use gcc -MMD. Only the C
compiler can know all the dependencies (ngg will miss files included
using verbatim statements, for example). However, the ngg compiler still
has to generate a .o rule because the .c and .h files that it depends on
won’t be present during the first run of make.
autoshallow is not allowed for classes with non-nullable
own fields. For nullable own fields, they will
be set to null.
autoshallow defines a function
_ngg_shallowcopy for a class and all its descendents that
returns a shallow copy of an object. As an example for its use, the
deepclone method of NggExpression (and its
descendents) in the code of the compiler itself calls
_ngg_shallowcopy before replacing certain fields with deep
clones.
classwide is recursively applied.
TODO
The keyword shadow can be used to declare prototypes of
foreign types, variables, and functions to be used in an ngg program.
One key difference from extern is that this declaration
doesn’t generate any code, even in the output .h files. A shadow
declaration is only for the ngg compiler to know about foreign
resources. This is exactly what you want when you have foreign libraries
that already provide .h files.
Let us consider an example. When you call puts in your
ngg code, the ngg compiler knows its type from the shadow declaration
shadow puts gives int takes s string present in
stdio.ngg, provided as part of the standard ngg
compile-time library. When you compile the C code of your program
generated by the ngg compiler, the C compiler knows about
puts from the declaration already present in
stdio.h.
The ngg compiler prints #include <stdio.h> in all
generated code by default. When you are writing bindings for other
libraries (say libjson-c), you will have to add appropriate
#include lines on your own (by putting
vh #include <HEADER.h> in your ngg code).