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 VALUE defines byte-wide compile-time constants stored in constants: HashMap<String, u8> and resolved during the second pass via resolve_number().
  • .DB embeds 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.rs enables 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →