How to Define and Use Data Constants and Strings in NanoCore Assembly
NanoCore's two-pass assembler supports compile-time constants via .CONST, raw byte embedding via .DB, and string literals via .STRING directives, storing values in a constants: HashMap<String, u8> and resolving them during the second pass.
NanoCore is a lightweight virtual machine with a two-pass assembler that allows developers to define and use data constants and strings in NanoCore assembly code through specialized directives. This guide examines the implementation in afaanbilal/nanocore and demonstrates how to embed compile-time values, raw bytes, and text literals in your .nca source files.
Defining Compile-Time Constants with .CONST
The .CONST directive creates symbolic names for byte-wide values that the assembler substitutes during compilation.
How the Assembler Parses Constants
In src/assembler.rs, the Assembler::is_constant method (lines 55-61) identifies lines beginning with .CONST. During the first pass, map_constants() scans the source and extracts name/value pairs, storing them in the constants: HashMap<String, u8> structure (lines 56-90).
First Pass: Building the Constant Table
The assembler iterates through every line, splitting tokens to find constant definitions. Each .CONST NAME VALUE entry populates the hash map before any instruction encoding occurs, enabling forward references.
Second Pass: Resolving Constant References
When encoding instructions, resolve_number() (lines 94-100) checks the constants map first (lines 95-97). If the token matches a defined constant, its numerical value substitutes for the operand.
; programs/constants_demo.nca
.CONST MAX_VAL 10 ; decimal literal
.CONST START 0x20 ; hexadecimal literal
LDI R0 MAX_VAL ; loads 10 into R0
LDI R1 START ; loads 0x20 into R1
ADD R0 R1 ; R0 = 0x2A
PRINT R0 ; prints '*'
HLT
The compiled binary contains the bytes:
02 00 0A ; LDI R0 10
02 01 20 ; LDI R1 0x20
09 01 ; ADD R0 R1
19 00 ; PRINT R0
00 ; HLT
Embedding Raw Data with .DB
The .DB directive inserts raw byte values directly into the program memory.
In src/assembler.rs, the second pass handles lines starting with .DB by parsing all subsequent tokens as decimal or hexadecimal numbers and appending them verbatim to the output buffer.
; programs/table_demo.nca
.DB 0x01 0x02 0x03 0x04 ; four bytes of data
LDI R0 0x00 ; start address of table (0)
LDR R1 R0 ; load first entry (0x01) into R1
PRINT R1 ; prints control character
HLT
The generated binary begins with 01 02 03 04 followed by the instruction encoding.
Storing String Literals with .STRING
The .STRING directive embeds UTF-8 text as sequential bytes.
In src/assembler.rs (lines 64-77), the assembler detects .STRING lines, extracts the text between the first and last double-quote, and pushes each character's UTF-8 byte value onto the program buffer.
; programs/hello.nca
.STRING "Hello"
LDI R0 0x00 ; address of the first character
PRINT_LOOP:
PRINT R0 ; prints the byte at address R0
INC R0
LDI R1 0x05 ; length of "Hello"
SUB R0 R1 ; stop when R0 == length
JNZ PRINT_LOOP
HLT
After assembly, the binary contains the ASCII codes 48 65 6C 6C 6F followed by the instructions.
Complete Example: Combining Constants, Data, and Strings
This mixed program demonstrates symbolic constants for memory addresses and string lengths:
; programs/mixed_demo.nca
.CONST MSG_ADDR 0x00
.CONST MSG_LEN 0x0B
.STRING "NanoCore!"
LDI R0 MSG_ADDR ; load start address of string
PRINT_LOOP:
PRINT R0
INC R0
LDI R1 MSG_LEN
SUB R0 R1
JNZ PRINT_LOOP
HLT
The constants provide symbolic names for the string's location and length. The .STRING directive embeds the text, and the loop prints the entire message before halting.
Compiled output (hex):
48 61 6E 6F 43 6F 72 65 21 ; "NanoCore!"
02 00 00 ; LDI R0 MSG_ADDR
19 00 ; PRINT R0
0D 00 ; INC R0
02 01 0B ; LDI R1 MSG_LEN
0B 01 00 ; SUB R0 R1
18 0A ; JNZ PRINT_LOOP
00 ; HLT
Summary
.CONST NAME VALUEdefines byte-wide compile-time constants stored inconstants: HashMap<String, u8>and resolved during the second pass viaresolve_number()..DBembeds raw decimal or hexadecimal bytes directly into the program memory..STRING "text"converts UTF-8 characters to sequential bytes for text storage and manipulation.- The two-pass architecture in
src/assembler.rsenables forward references and symbolic substitution before instruction encoding.
Frequently Asked Questions
What is the maximum value for a NanoCore constant?
NanoCore constants are stored as u8 values in the constants: HashMap<String, u8>, limiting them to the range 0-255 (one byte). Decimal and hexadecimal literals (e.g., 255 or 0xFF) are parsed accordingly during the first pass in map_constants().
Can I use constants as memory addresses in NanoCore assembly?
Yes. Constants defined with .CONST can represent memory addresses, string lengths, or immediate values. When used with load instructions like LDI, the assembler resolves the constant name to its numeric value during the second pass via resolve_number() before encoding the instruction.
How does NanoCore handle string encoding?
The .STRING directive in src/assembler.rs extracts text between double quotes and pushes the UTF-8 byte values of each character onto the program buffer. This means ASCII characters occupy one byte each, while multi-byte UTF-8 characters are stored as sequential bytes that can be accessed individually via memory operations.
Are forward references supported for constants?
Yes. The two-pass design of the NanoCore assembler enables forward references for constants. During the first pass, map_constants() collects all .CONST definitions into the constants hash map before the second pass begins encoding instructions. This allows you to use a constant name before its definition appears in the source code.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →