nrk (/ˈanɑːrki/
) is a small programming language with a bytecode interpreter implementation written entirely in C99.
The language is inspired by Bob Nystrom's "Crafting Interpreters" with modifications and enhancements.
- Bytecode Virtual Machine: Executes instructions efficiently with a stack-based architecture
- C99 Implementation: Written in standard C99 for maximum portability
- REPL: Interactive Read-Eval-Print Loop with history persistence and line editing
- File Execution: Run
nrk
script files directly - Expression Evaluation: Supports arithmetic expressions, string operations, and variables
- Type System: Dynamic typing with runtime type checking
- Operators: Arithmetic, comparison, logical, bitwise, and postfix operations
- String Handling: String literals, concatenation, interning, and template strings
- Memory Management: Efficient memory allocation with garbage collection foundations
- Variable Scoping: Support for both global and local variables with lexical scoping
- C compiler (clang recommended)
- Make
# Standard build
make all
# Debug build with all debugging flags enabled
make debug
# Release build (no debug flags)
make release
REPL Mode:
./bin/nrk
File Execution:
./bin/nrk path/to/script.nrk
Debugging:
# Run with GDB
make gdb-debug
# Run tests
make test
NRK uses semicolons to terminate statements:
print "Hello, world!";
var x = 10;
NRK currently supports the following primitive types:
- Numbers:
42
,3.14159
- Strings:
"Hello, world!"
- Booleans:
true
,false
- Nil:
nil
(represents absence of a value)
Template strings are also supported for more dynamic string creation:
`This is a ${1+1} in a template string` // "This is a 2 in a template string"
`Nested templates: ${`inner ${42}`}` // "Nested templates: inner 42"
1 + 2 // Addition: 3
10 - 5 // Subtraction: 5
3 * 4 // Multiplication: 12
10 / 2 // Division: 5
~5 // Bitwise NOT: -6
8 >> 2 // Bitwise right shift: 2
2 << 3 // Bitwise left shift: 16
12 & 5 // Bitwise AND: 4
12 | 5 // Bitwise OR: 13
12 ^ 5 // Bitwise XOR: 9
1 == 1 // Equal: true
2 != 2 // Not equal: false
3 > 2 // Greater than: true
4 < 3 // Less than: false
5 >= 5 // Greater than or equal: true
6 <= 7 // Less than or equal: true
!true // Logical NOT: false
!!0 // Double NOT: false (0 converts to false first)
!nil // NOT nil: true (nil is falsey)
var a = 5;
a++ // Increment: a becomes 6
a-- // Decrement: a becomes 5
"Hello, " + "world!" // Concatenation: "Hello, world!"
Variables are declared using the var
keyword:
var a = 5;
var name = "Alice";
var isActive = true;
Variables can be modified after declaration:
var counter = 0;
counter = 10;
counter++; // Now 11
Compound assignment is also supported:
var x = 5;
x += 3; // x = x + 3: x becomes 8
x -= 2; // x = x - 2: x becomes 6
x *= 2; // x = x * 2: x becomes 12
x /= 4; // x = x / 4: x becomes 3
NRK supports complex expressions with operator precedence:
1 + 2 * (3 + 4) / 5 // 3.8
print "Hello, world!";
print 2 + 2; // 4
print a + b; // Prints the sum of variables a and b
NRK supports lexical scoping with blocks:
{
var x = 10;
print x; // 10
{
var y = 20;
print x + y; // 30
}
// y is not accessible here
}
// x is not accessible here
NRK is implemented as a bytecode virtual machine with a stack-based architecture:
- Scanner: Tokenizes source code into a stream of tokens
- Compiler: Parses tokens and generates bytecode instructions
- Virtual Machine: Executes bytecode instructions
Grammar:
program → declaration* EOF ;
declaration → varDecl
| statement ;
varDecl → "var" IDENTIFIER ( "=" expression )? ";" ;
statement → exprStmt
| printStmt
| block ;
block → "{" declaration* "}" ;
exprStmt → expression ";" ;
printStmt → "print" expression ";" ;
Variable declaration:
varDeclaration() -> parseVariable() --Constant--> Compile Initializer -> defineVariable()
- VM: Stack-based virtual machine that executes bytecode
- Compiler: Turns source code into bytecode
- Scanner: Tokenizes source code for the compiler
- Chunk: Represents bytecode and related metadata
- Value: Represents runtime values (numbers, booleans, nil, strings)
- Object: Manages heap-allocated objects like strings
- Memory: Handles memory allocation, reallocation, and garbage collection
- Debug: Tools for inspecting bytecode and execution
- REPL: Interactive environment with history, line editing, and history persistence
- Table: Hash table implementation for string interning and variable lookup
NRK is currently in early development (v0.0.1) and implements:
- Basic arithmetic operations
- Comparison operations
- Boolean logic
- Bitwise operations
- Variables and assignment (global and local)
- Print statements
- Postfix operators (++, --)
- String operations and template strings
- String interning with hash tables
- Memory management foundations with garbage collection
- Lexical scoping with blocks
The interpreter includes powerful debugging features that can be enabled via the Makefile:
# Enable all debug flags
make debug
Individual debugging options can also be controlled in common.h
:
DEBUG_PRINT_CODE
: Disassembles and prints bytecode after compilationDEBUG_TRACE_EXECUTION
: Traces VM execution step by step, showing stack stateDEBUG_SCAN_EXECUTION
: Shows detailed scanning process informationDEBUG_COMPILE_EXECUTION
: Provides detailed compilation process logs
All debug flags can be simultaneously enabled by defining NRK_DEBUG_ALL
.
The new build system also supports:
- Cleaner directory structure (src, obj, bin)
- Better dependency tracking
- GDB integration
- Test running capabilities
Future plans include:
- Control flow (if/else, loops)
- Functions and closures
- Classes and inheritance
- Full implementation of compound assignment operators
- String interning is implemented using a hash table for efficient string comparison
- Extended constant pool via
OP_CONSTANT_LONG
allows for more than 256 constants - Memory management uses Flexible Array Members (FAM) for efficient string storage
- Local variable handling uses direct stack slot access for performance
- Planned improvements:
- Extend table support to other immutable objects besides strings as keys
- Optimize compiler pointer indirections in critical paths
- Complete implementation of compound assignment operators