The nguigen Programming Language
Introduction
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
nonfinalso the unnecessary overhead for dynamic dispatch support won’t be thereNo 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
shadowandflenumthat 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.
Rust vs ngg
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.
Who Is This Document For?
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.
Syntax
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 cmeansa + b * c.sum 1 sum 2 3means1 + (2 + 3)whilesum sum 1 2 3means(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
ato executebfollowed byccan be written in a single line asif a b; c;;. Note that there are two semicolons in the end – one to terminate the statementcand one to terminate the blockif 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. Solaptop1.battery.capacityin Java-like languages would becapacity\battery\laptop1in ngg. It starts making sense when you read backslash as “of”.
Type System
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 and Strings
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 vs Classes
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 typeMyClassautomatically translates toMyClass *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.
Flenum
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
;
Memory Management
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.
Immortal Classes
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.
Unique Ownership
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].
Strong and Weak Reference Counting
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
;
Object-Oriented Programming
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]]
;
Miscellaneous Notes
Abstract classes need to be marked explicitly using the
abstractkeyword 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.
GUI Programming
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
;
Reactive Programming
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;
;
Metaprogramming
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.
Miscellaneous Notes
This chapter dumps a collection of notes that have not been placed in any specific chapter.
When generating C code, ngg avoids redundant parentheses by referring
to the operator precedence of C. However, it may print parentheses that
are not strictly required if it improves clarity. For example,
or x (and y z) could be translated as
x || y && z, but ngg prints
x || (y && z). This is also encouraged by the
following GCC warning: “warning: suggest parentheses around ‘&&’
within ‘||’ [-Wparentheses]”
Command-Line Options
-I
-oc
-oh
-oh-auto
-t
-v
--fallback-incpath
--c-include-guard
--c-include-guard-prefix
--dbg-asmify
--dbg-ngg-after-modtree
--dbg-ngg-after-widserial
--dbg-log-indent
--dbg-print-refers
--no-check-lvalue-type
--no-remove-noeffect
--ngd
--ngd-use-ngh
--dbg-no-assert-indent
--require-version
--tree-on-parse-error
--c-no-auto-includes
--c-no-auto-includes-from-refer
--no-fallback-incpath
--x-allow-modtree-escape
--x-allow-nullability-errors
--x-allow-ownership-errors
--x-allow-unused-vars
--x-assigncheck-strict-float
--x-defstruct-with-nullables
--x-enforce-ownership
--x-leak-autodel-reassign
--x-local-on-stack
--x-nnptr-init-nil
--x-no-ngh-for-non-ngh
--x-nop-free
--x-rc-for-all-classes
--test
--version
--help
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.
Things to Avoid
Here’s a list of things that one should avoid while writing ngg code.
The code generated by the ngg compiler may use these features in a safe
manner, but manual use can be error-prone and mislead the compiler.
There is a plan to restrict these features using some mechanism similar
to Rust’s unsafe.
Pointers, unless you are writing a new interface for some existing C library.
Arrays. Use
vectorandowningvectorinstead.malloc,free, etc. Usenewand the built-in ownership mechanism instead.Verbatim target code using
v,vh, etc. (It’s a hack used by the ngg compiler and libraries, expected to be phased out eventually.)
Building ngg Projects
Work is in progress to support the command ngg build.
This command, in combination with additional arguments and/or a manifest
file, should be able to build a debug build as well as tarballs and
packages for various target platforms. Until then, one has to depend on
make. Thankfully, there are some solutions that simplify
the generation of Makefiles.
helpers/automk.sh: scans an ngg project directory and generates a Makefile with appropriate dependencies for each file. This predates the .ngd mechanism (see below). Please refer to the source code of the script for command-line options.helpers/Makefile-using-ngd: one could copy this file to their project with necessary modifications.
The file FILE.ngd is generated by the ngg compiler and
records the source files referred to by FILE.ngg
(comparable to the .d files generated by gcc).
This helps make decide when to rebuild what components. ngg
generates these only when --ngd is set, but
helpers/Makefile-using-ngd mentioned above takes care of
this as well.
The generated ngd file will contain the following in the case of C output:
- A .c target with .ngg prerequisites (or .ngh in case
--ngd-use-nghis set, except for the first one) - All non-ngg dependencies such as files embedded using compiletime evaluation (part of the above rule)
- A .o target with all the .ngg files from (1) listed as .h prerequisites
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.
Using Foreign Libraries
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).
HTML Template Rendering
Many real-world Web applications will have to display the same kind of information related to different entities in the same format. Think of a school management system that displays the profile of each student. HTML templating is a neat practice in which a dummy page with all the structure and styling, called a “template”, is kept separate from the data and the code that populates the template with said data. This separation not only makes the code maintainable, but improves the security as well (any good template rendering system will prevent issues like Cross-Site Scripting).
Support for HTML templating in nguigen is different from most frameworks out there – rather than rendering the template at run-time, it helps you generate template-specific rendering code at compile-time. This is meant to improve performance. (One could also write a dynamic rendering engine if one wants, for nguigen is a general purpose language.)
HTML Templates and JSON Files
ngg follows the conventions of the Django Template Language (DTL)
whenever possible. Feature support is fairly limited, but some control
flow (if and for) and an option to turn off
automatic HTML escaping are provided. HTML templates should be saved
with an extension .htmg, which the ngg compiler recognizes.
Following is page.htmg:
<!DOCTYPE HTML>
<html charset="UTF-8">
<head>
<title>{{ title\pageinfo }}</title>
<meta name="description" value="{{ description\pageinfo }}">
</head>
<body>{{ body-text\pageinfo }}</body>
</html>
If compiled with the ngg compiler (ngg page.htmg), the
above file will produce ngg code consisting of a series of
printf and puts statements, along with calls
to the ngg-provided escape-string-for-html function. The
idea is that, once compiled and run, this generated ngg code should
produce HTML code populated with data from the object
pageinfo.
We declare the type PageInfo in
json.ngg:
// Nandakumar Edamana
// 2026-02
include 'json-c.ngg'
struct PageInfo given [ autojson-decoder true ]
title string
description string
body-text string
;
We set the flag autojson-decoder true for
PageInfo so that we can use our program as a static site
generator that generates similar-formatted HTML pages from a bunch of
JSON files. Following is a sample main file that populates a
PageInfo object from JSON, which is then passed to the
template rendering function. Note that there is no requirement that the
data should come from a JSON file or a JSON string. An object of
PageInfo could be created and passed by other means.
// Nandakumar Edamana
// 2026-01, 2026-02
include './json.ngg'
include 'render.ngg'
fun $main
// TODO read from stdin
var jsonstr / '{ "title": "Is n < n + 1?", "description": "Just another page.", "body_text": "Hello, world!" }'
var obj PageInfo / =page-info-from-json-string/[jsonstr]
=render/[obj]
;
The main file calls the function render, which simply
wraps the ngg code generated from page.htmg. Following
Makefile automates the addition of boilerplate to the said ngg code (the
Makefile is part of a test suite that may contain extraneous
targets).
all: test-PHONY
%.h: %.c
%.c: %.ngg
ngg -oc "$@" -oh-auto --c-include-guard-prefix HTMLGENTEST "$<"
main.c: render.ngg
render.ngg: page.htmg
echo 'include html;' > render.ngg.tmp # for escaping
echo 'include vstring;' >> render.ngg.tmp
echo 'fun render takes pageinfo PageInfo' >> render.ngg.tmp
ngg page.htmg >> render.ngg.tmp
echo ';' >> render.ngg.tmp
mv render.ngg.tmp render.ngg
prog-PHONY: main.c
cc -Wall -o prog.elf $$(pkg-config --cflags json-c) main.c $$(pkg-config --libs json-c)
test-PHONY: prog-PHONY
./prog.elf > /dev/null
test -z "$$(./prog.elf|diff test-expected.html -)"
clean:
rm -f prog.elf *.[ch] render.ngg render.ngg.tmp *.nggtmp
TODO
TODO a simpler example without JSON parsing
TODO How htmg processing can be used as a general macro processing mechanism by turning off autoescape