Skip to content

Keywords and Operand types

🚧 Under Construction

This page is actively being written. Content may be incomplete or change.

Runestaff’s type system closely follows the Java class file format. Every token in a .rns source file is parsed as one of the types described below. The parser determines a token’s type mostly by inspecting its first character.


Keywords are reserved words that carry special meaning in the language syntax. In Runestaff, keywords are positionally reserved — they are only treated as keywords in certain positions, and can be used as plain identifiers everywhere else. Think of them as “soft” reserved words.

For example, .super is a directive keyword, but it can also appear as an identifier in the operand position:

.super com/example/MySuperClass
; ".super" is the directive keyword
; "com/example/MySuperClass" is an identifier
.super .super
; or
.super public
; here ".super" and "public" are in operand position — treated as identifiers, not keywords

This applies to any keyword: its meaning is determined entirely by where it appears, not by what it is.


Directives always start with a dot (.) and declare structural elements: classes, methods, fields, and so on. They consume operands of the types described in the Operands section below.

See the Directives page for the full list.


Access flags control the visibility and behaviour of classes, methods, and fields. Common examples: public, private, protected, static, final, abstract. Access flag is optionally present and should be placed right after the directive keyword (TODO: examples on methods and fields).:

.class public final MyClass

I don’t enforce a specific order for access flags:

.class final public MyClass

TODO: Interfaces, annotations, enums blabla TODO: Say about some rules and JVMS warnings


Operands are the values that directives and instructions operate on. They are mostly based on constant pool entries, because almost all operands are encoded as constant pool references in the resulting bytecode.

Runestaff supports implicit type inference for operands — the parser determines the operand type from its syntax. A token starting with a digit is parsed as an integer literal, one starting with " is a string, and so on.


Identifiers are the most general operand type. An identifier can refer to almost any constant pool entry without requiring explicit type syntax. The assembler resolves it to the appropriate constant pool type based on context.

For example, a superclass reference is stored as a CONSTANT_Class entry in the constant pool, but you just write it as a plain identifier:

.super java/lang/Object
; "java/lang/Object" resolves to a CONSTANT_Class entry

A method name or descriptor resolves to CONSTANT_Utf8:

.method public static main ([Ljava/lang/String;)V
; "main" and "([Ljava/lang/String;)V" resolve to CONSTANT_Utf8 entries

Sometimes you need an identifier that starts with a digit, contains spaces, or includes characters that would break normal parsing. Use the raw identifier syntax for this:

#"..." — raw identifier

.super #"42"
; An identifier literally named "42", not the integer 42
.super #"my class name"
; An identifier with spaces

The following escape sequences are supported inside #"...":

SequenceMeaning
\"Double quote
\\Backslash
\nNewline
\tTab
\rCarriage return
\0Null
\bBackspace
\fForm feed

Integers are supported only in decimal form for now. Hexadecimal and binary literals are not yet supported.

As JVM supports only 32-bit integers, the valid range is from -2147483648 to 2147483647. Values outside this range will cause an error.


String literals are enclosed in double quotes and support the same escape sequences as raw identifiers:

ldc "Hello, World!"
ldc "line one\nline two"
ldc "she said \"hi\""

String literals are encoded as CONSTANT_String entries in the constant pool.

Note: Multi-line string literals are not currently supported. A string must be written on a single line.


Not yet supported.


By default, Runestaff infers the constant pool type from context. However, since Runestaff is a sister project to Lagertha VM and is designed to produce any bytecode — including intentionally invalid bytecode — you may need to force a specific constant pool type regardless of context.

Type hints let you do exactly that. The syntax is:

@type components...

Where type is the constant pool type name followed by the space-separated components of that constant pool entry.

For example, to force a superclass reference to point to a CONSTANT_Methodref instead of a CONSTANT_Class:

.super @methodref java/lang/Object toString ()Ljava/lang/String;
; Forces the operand to a method reference, regardless of what .super normally expects

Supported type hints:

Type hintConstant pool entryComponentsExample
@utf8CONSTANT_Utf8Identifier@utf8 MyClass.class
@intCONSTANT_IntegerDecimal or hex integer@int 42
@stringCONSTANT_StringString literal@string "Hello, World!"
@classCONSTANT_ClassClass name identifier@class java/lang/Object
@methodrefCONSTANT_MethodrefClassName MethodName Descriptor@methodref java/lang/Object toString ()Ljava/lang/String;
todo