[Home] [Back]

GRK Language Reference

Download

grk_debug.exe — GRK interpreter (Windows x86, debug build) | grk_debug.apk — GRK interpreter (Android, debug build)

Example programs: [programs/]

Overview

GRK (also written GORK) is a compact interpreted programming language designed to run on severely resource-constrained hardware -- from ATtiny85 microcontrollers with 512 bytes of SRAM up to desktop x86 machines. The same source text runs identically on every target.

Design goals:


Source Format

A GRK program is a sequence of ASCII characters. Whitespace (spaces, tabs, newlines) is ignored everywhere except inside string literals.

Comments

Text enclosed between two _ underscores is a comment and is skipped entirely:

_ this is a comment _
0n 42    _ set integer 0 to 42 _

Comments may appear anywhere whitespace is allowed. They cannot be nested.

String literals

Text enclosed in # delimiters is a string literal. Any character may appear inside, including a literal newline character (which will be output as a newline).

#Hello, world!#

The # delimiter itself cannot appear inside a literal.

Expression termination

The interpreter reads one expression at a time. An expression ends when the next character is not a valid continuation of that expression (not an operator, not a value, not a type character). No explicit terminator is required in most cases; the : separator is used only where a loop or conditional boundary must be marked explicitly.

Unrecognised characters outside a string literal or comment raise an interpreter error.


Variable Types

GRK has four variable types. Every variable is identified by a numeric index and a single-character type tag.

Type tagC type (desktop)Description
nint32_tSigned integer
ffloatIEEE 754 single (or Q8.8 fixed-point on AVR)
bbit in uint8_tBoolean (y = true, n = false)
schar[]Fixed-length string slot

Variables are referenced as <index><type>, for example 0n, 3f, 12s, 1b.

Indices start at 0. The maximum index and string slot length depend on the platform profile (see Platform Profiles). All slots are zero/empty-initialised before each program run.

There is also a group (function) type g, described in the Groups section.


Assignment

Assign a value to a variable by writing the variable name followed by the value:

0n 42          integer 0  = 42
1f 3.14        float 1    = 3.14
2b 1           boolean 2  = true   (1/y/Y/t/T = true, 0/n/N/f/F = false)
3s#Hello!#     string 3   = "Hello!"

The value may be a literal, another variable of the same or compatible type, or a full arithmetic expression:

0n 1n          copy integer 1 into integer 0
0n 2n + 3n     integer 0 = integer 2 + integer 3
1f 0n          integer 0 cast to float, stored in float 1

Arithmetic and Expressions

Operators for integer (n) and float (f) variables:

OperatorMeaningPrecedence
*MultiplyHigh
/DivideHigh
%ModuloHigh
+AddLow
-SubtractLow

Standard order of operations applies: *, /, % bind before + and -.

0n 2+3*4       0n = 14   (not 20)
1n 10-2*3      1n = 4
2f 1.0/3.0     2f = 0.33
3n 17%5        3n = 2

Self-modify

An operator immediately after a variable name modifies the variable in place using its current value as the left-hand side:

0n+1           increment 0n
0n*2           double 0n
0n%3           0n = 0n mod 3
1f/2.0         halve 1f

The full expression is evaluated before being written back:

0n+3*2         0n = 0n + (3*2), i.e. adds 6

Conditionals

A comparison operator after a variable name starts a conditional. The code between the comparison and the next : separator executes only when the condition is true. When the condition is false the interpreter skips forward to the :.

Comparison operators

OperatorMeaning
=Equal
!Not equal
>Greater than
<Less than
0n=5 o#five# :          prints "five" only when 0n equals 5
1n>0 o#positive# :      prints "positive" only when 1n > 0
0s=#hello# o#yes# :     prints "yes" only when string 0 equals "hello"

The right-hand side may be a literal value or another variable:

0n>1n [code] :          compare integer 0 to integer 1
0s!1s [code] :          true when string 0 and string 1 differ

AND and OR chaining

Multiple conditions may be chained. All conditions share one code body and one :.

& (AND) -- all conditions must be true:

0n>0 & 0n<10 o#single digit# :

? (OR) -- at least one condition must be true:

0n=0 ? 0n=5 o#zero or five# :

Chains are evaluated left to right; & and ? may be mixed in one chain.

Else

A : may be followed immediately by ; to introduce an else block. The else block runs only when the preceding condition was false, and ends at its own :.

0n>5 o#big# :; o#small# :

Multiple else-if branches can be chained by starting each else body with another conditional:

0n>89 o#A# :;
0n>79 o#B# :;
0n>69 o#C# :;
      o#D# :

Loops

Basic loop: l

l rewinds the program counter backward to the nearest : separator or group-body delimiter $ (whichever comes first). Combined with a conditional, this creates a while-style loop:

0n 5
:0n>0 o0n o## 0n-1 l:

Output:

5
4
3
2
1

The loop anchor is the : before the condition. The final : is only reached when the condition is false, ending the loop.

Important: there must be no : separators in the loop body between the anchor and l. If conditional logic is needed inside an l loop body, put it in a group.

Group loop: lg

lg is identical to l but only stops at a group-body delimiter $, not at :. It is used inside a group to loop within the group body:

1g$
  0n>0 o#-# 0n-1 lg:
$

Calling group 1 with 0n=4 outputs four lines each containing -. The $ at the end returns from the group (see Groups).

Important: lg rewinds to the opening $ of the group body. Any code between $ and the condition is re-executed on every pass, so initialisation must happen outside the group. Set counter variables before calling the group.

Counted loop: lv N

lv executes its body exactly N times. The : before the body is the loop anchor; lv rewinds to it on each repeat.

: o#hi # lv 5

Output: five lines each containing hi .

The count N may be a literal integer or an integer variable:

0n 3
: o#go # lv 0n

Up to three lv loops may be nested; each instance tracks its own counter independently.

: : o#.# lv 2 o##  lv 3

Output: three lines of .. (two dots per line, three lines total).


Groups

Groups are GRK's functions. They are identified by an integer index and the type tag g.

Defining a group

<index>g$ opens a group body. The closing $ ends the body and returns to the call site. The body is skipped during normal execution when its definition is encountered.

1g$ o#hello from group 1# $

Calling a group

Write the group name alone to call it:

1g

The interpreter pushes the return address, jumps to the group body, executes until the closing $, then returns.

Forward references

A group may be called before it is defined in the source text. When an undefined group is called, the interpreter scans forward through the remaining source to locate the definition. If found it is registered and called normally; if not found the call is silently skipped.

3g                     call before definition
x                      end of main program
3g$ o#found forward# $ definition after x

Nesting and recursion

Groups may call other groups. The call stack depth is fixed by the platform profile (GRK_CALL_STACK_DEPTH). Calls beyond the stack limit are silently dropped.

1g$ 2g $
2g$ 3g $
3g$ o#depth 3# $
1g

Output

The o command outputs one or more comma-separated values and then always emits a newline.

o#text#              string literal
o0n                  integer variable 0
o1f                  float variable 1 (2 decimal places)
o2b                  boolean variable 2 (y or n)
o3s                  string variable 3
o42                  integer literal 42
o3.14                float literal 3.14
o r 0,9              random integer in [0, 9]
o3s 1,4              string variable 3, characters 1-4 (slice)
o##                  bare newline (empty literal)

Multiple items are chained with ,:

o#score: #,0n,# pts#       score: 42 pts
o0n,# / #,1n               42 / 7
o#name: #,2s,#  val: #,0n  name: hello  val: 99

Each o expression ends with exactly one newline regardless of how many items it contains. To output a blank line use o##.

Booleans are output as y (true) or n (false). Floats are formatted to two decimal places.


Input

The i command reads one line of input and stores the parsed result in a variable.

FormReads
i0nSigned integer into integer 0
i1fDecimal float into float 1
i2b1/y/t = true, 0/n/f = false
i3sRaw text into string 3

On desktop the platform reads from stdin. On a microcontroller, i is wired to whatever input source the platform provides (keypad, UART, etc.).


String Operations

Assignment and concatenation

Assign a literal:

0s#Hello#

Append a +-chained sequence of values onto an existing slot:

0s + # world!#        appends " world!" to 0s
0s + 1n + #px#        appends the value of 1n, then "px"

Assign with an inline concat chain:

0s#price: # + 1n + # USD#

Items in a + chain may be #literal#, <idx>n, <idx>f, <idx>b, <idx>s, an integer literal, a float literal, a random number r <lo>,<hi>, or a string slice <idx>s <a>,<b>.

Slice (copy a range)

Copy characters [start, end] (inclusive, 0-based) from one string slot to another:

2s 1s 2,5      2s = characters 2 through 5 of 1s

A slice may also appear as an atomic value in output or conditionals without being assigned to a variable first:

o1s 2,5                  output characters 2-5 of 1s directly
o#prefix: #,1s 0,3       comma-chain: prefix then a slice
0s=1s 0,4                compare 0s to characters 0-4 of 1s

The slice bounds may be integer literals or integer variables:

2s 1s 0n,1n    copy from index 0n to index 1n

Insert (replace a range)

Replace characters [start, end] of a string slot with a literal:

2s 1,3,#ord#   replace characters 1-3 of 2s with "ord"

Copy with modification

Modify a source slot and copy the result to a destination in one step:

4s 2s 1,3,#ord#    modify 2s then copy it to 4s; both end up with the new value

Comparison

Use = and ! to compare a string variable against a literal or another slot:

0s=#hello# o#match# :
0s!1s      o#differ# :

A slice range may be appended to the right-hand side variable to compare against only part of it:

0s=1s 0,4    true when 0s equals characters 0-4 of 1s

Indirect Indexing

The @ operator uses the value of an integer variable as the index for a subsequent variable access. This enables integer arrays and dynamic dispatch.

0n 5
0n@n 42        n[5] = 42  (write to integer slot 5)
o0n@n          outputs n[0n] = n[5] = 42

The variable before @ must be an integer (n). The type tag after @ may be any of n, f, s, or b:

0n 2
0n@s#hi#       s[2] = "hi"
o0n@f          output f[0n]

Indirect indexing is recognised in all contexts: assignment, expressions, conditionals, output, input, and string concat chains.

Array pattern:

0n 10  0n@n 100       store 100 in integer slot 10
0n 11  0n@n 200       store 200 in integer slot 11
0n 12  0n@n 300       store 300 in integer slot 12

1n 10                 array pointer
:1n<13 o1n@n,# # 1n+1 l:

Output:

100
200
300

Random Numbers

r <lo>,<hi> generates a uniformly distributed random integer in the range [lo, hi] inclusive. The bounds may be integer literals or integer variables.

0n r 1,6       roll a six-sided die
1n r 0n,2n     random integer between 0n and 2n

r may appear anywhere an integer value is valid: assignment, arithmetic expressions, output, and string concat chains.

o r 1,100               output a random number 1-100
0s + r 0,9              append a random digit to string 0s
0n r 0,9  0n>5 o#high# : conditional on a random roll

On desktop, r uses the C standard library rand(). On AVR targets it uses a 32-bit linear congruential generator seeded at startup.


Special Instructions

InstructionMeaning
xEnd program; also usable mid-program
:Separator -- ends a conditional body, loop anchor
;Else marker -- immediately follows : to start an else block
cClear the output display (platform-dependent)
p<ms>Pause for ms milliseconds (literal integer)
p<idx>nPause for the value of integer variable <idx>

c calls the platform clear-screen stub. On desktop (Windows) it runs cls; on POSIX it emits an ANSI erase sequence. On bare AVR targets it is a no-op by default.

Pause is a no-op on desktop unless the platform stub is replaced.


Platform Profiles

All memory sizing is set at compile time by selecting a profile in grk_config.h.

ProfileIntegerFloatBooleansStringsStackProgram
GRK_PROFILE_ATTINY8516 × int164 × Q8.816 bits3 × 8 chars4256 B
GRK_PROFILE_UNO32 × int168 × Q8.832 bits16 × 16 chars81 KB
GRK_PROFILE_DESKTOP64 × int3264 × float64 bits100 × 128 chars6432 KB

On ATtiny85 and Uno, floats use Q8.8 fixed-point arithmetic (scale factor 256). On desktop, standard IEEE 754 float is used. The GRK source syntax for floats is identical across all profiles.


Complete Example

The program below exercises every major feature of the language in a single cohesive program. Annotated sections explain which feature each part demonstrates.

What the program does

Stores five exam scores in an integer array (slots 10-14), prints each score with its letter grade, computes the sum and pass count, calculates the float average, builds a formatted summary string, and finishes with a verdict and several miscellaneous feature demonstrations.

Expected output

=== Score Report ===
--- Scores ---
  slot 10:  85  [B]  *
  slot 11:  42  [F]
  slot 12:  95  [A]  *
  slot 13:  67  [D]  *
  slot 14:  55  [F]
-------
passes: 3/5  avg: 68.80

verdict: above average

first 7: passes:

updated: scores: 3/5  avg: 68.80

10 even
11 odd
12 even
13 odd
14 even

slot 12: value is 95, not 100
(forward ref executed successfully)

Source (annotated)

[INDIRECT INDEXING -- store five scores in integer slots 10-14]

0n 10  0n@n 85
0n 11  0n@n 42
0n 12  0n@n 95
0n 13  0n@n 67
0n 14  0n@n 55

[BOOLEAN variable -- flag: has any score earned an A?]

0b 0

[GROUP 1 -- assign letter grade to 1s based on 0n@n; sets 0b if grade is A]
[ELSE chaining with ; demonstrates multi-branch conditional]

1g$
  0n@n>89 1s#A# 0b 1 :;
  0n@n>79 1s#B#       :;
  0n@n>69 1s#C#       :;
  0n@n>59 1s#D#       :;
           1s#F#       :
$

[GROUP 2 -- draw a horizontal rule of 3n dashes; builds string in 5s then outputs it]

2g$
  5s##  6n 0
  :6n<3n 5s + #-# 6n+1 l:
  o5s
$

[GROUP 3 -- print one score row; builds full line in 5s then outputs it in one o call]
[Uses indirect 0n@n for score; appends * for passing scores]

3g$
  1g
  5s#  slot # 5s + 0n + #:  # + 0n@n + #  [# + 1s + #]#
  0n@n>59 5s + #  *# :
  o5s
$

[GROUP 5 -- accumulate one slot: add score to sum, count passes]
[Extracted into a group so lv loop body has no nested : before lv]

5g$
  1n + 0n@n
  0n@n>59 2n+1 :
$

[GROUP 4 -- print even/odd line for slot 0n]

4g$
  2n 0n%2
  2n=0 5s#even# ; 5s#odd# :
  o0n,# #,5s
$

[OUTPUT -- print header]

o#=== Score Report ===#

[LV LOOP -- print all five scores; 0n starts at 10, increments each iteration]

o#--- Scores ---#
0n 10
: 3g 0n+1 lv 5

[LV LOOP (second independent counter) -- iterate slots 10-14, calling group 5]

1n 0  2n 0
0n 10
: 5g 0n+1 lv 5

[FLOAT variable -- compute average; self-modify with / operator]

0f 1n
0f/5

[STRING CONCAT CHAIN -- build summary string 4s]

4s#passes: # 4s + 2n + #/5  avg: # + 0f

[GROUP CALL + OUTPUT -- draw rule then print summary and blank line]

3n 7  2g
o4s
o##

[AND chain (&) and OR chain (?) in verdict -- demonstrates both logical operators]
[ELSE (;) for multi-branch output]

0f>79.99 & 0b=1 o#verdict: excellent#     :;
0f>79.99 ? 0b=1 o#verdict: above average# :;
0f>59.99        o#verdict: good#           :;
                o#verdict: needs work#     :
o##

[STRING SLICE -- copy first 7 characters of 4s into 5s, output with comma chain]

5s 4s 0,6
o#first 7: #,5s
o##

[STRING INSERT -- replace "passes" with "scores" in 4s]

4s 0,5,#scores#
o#updated: #,4s
o##

[BASIC L LOOP with MODULO -- show even/odd for each slot index]
[Conditional logic is inside group 4 so no : appears between the loop anchor and l]

0n 10
:0n<15 4g 0n+1 l:
o##

[NOT-EQUAL ! with AND & -- check that slot 12 holds 95 but is not 100]

0n 12
0n@n=95 & 0n@n!100 o#slot 12: value is 95, not 100# :

[FORWARD REFERENCE -- group 9 is defined after x]

9g
x
9g$
  o#(forward ref executed successfully)#
$

Complete source (no annotations)

0n 10  0n@n 85
0n 11  0n@n 42
0n 12  0n@n 95
0n 13  0n@n 67
0n 14  0n@n 55
0b 0
1g$
  0n@n>89 1s#A# 0b 1 :;
  0n@n>79 1s#B#       :;
  0n@n>69 1s#C#       :;
  0n@n>59 1s#D#       :;
           1s#F#       :
$
2g$
  5s##  6n 0
  :6n<3n 5s + #-# 6n+1 l:
  o5s
$
3g$
  1g
  5s#  slot # 5s + 0n + #:  # + 0n@n + #  [# + 1s + #]#
  0n@n>59 5s + #  *# :
  o5s
$
5g$
  1n + 0n@n
  0n@n>59 2n+1 :
$
4g$
  2n 0n%2
  2n=0 5s#even# ; 5s#odd# :
  o0n,# #,5s
$
o#=== Score Report ===#
o#--- Scores ---#
0n 10
: 3g 0n+1 lv 5
1n 0  2n 0
0n 10
: 5g 0n+1 lv 5
0f 1n
0f/5
4s#passes: # 4s + 2n + #/5  avg: # + 0f
3n 7  2g
o4s
o##
0f>79.99 & 0b=1 o#verdict: excellent#     :;
0f>79.99 ? 0b=1 o#verdict: above average# :;
0f>59.99        o#verdict: good#           :;
                o#verdict: needs work#     :
o##
5s 4s 0,6
o#first 7: #,5s
o##
4s 0,5,#scores#
o#updated: #,4s
o##
0n 10
:0n<15 4g 0n+1 l:
o##
0n 12
0n@n=95 & 0n@n!100 o#slot 12: value is 95, not 100# :
9g
x
9g$
  o#(forward ref executed successfully)#
$

Quick Reference

VARIABLES      0n 42        integer 0 = 42
               0f 3.14      float 0 = 3.14
               0b 1         boolean 0 = true
               0s#text#     string 0 = "text"
               0n 1n        copy integer 1 to integer 0

ARITHMETIC     + - * / %    standard operators; * / % before + -
               0n+1         self-modify (increment)
               0n 2n*3+1    full expression

CONDITIONALS   0n=5 ... :   equal
               0n!5 ... :   not equal
               0n>5 ... :   greater than
               0n<5 ... :   less than
               0s=#hi# ...: string equal
               & 1n>0       AND chain
               ? 1n=0       OR chain
               :; ... :     else block

LOOPS          :cond body l:    basic loop (rewinds to nearest :)
               :cond body lg:   group loop (rewinds to nearest $)
               : body lv N      counted loop (N times)

GROUPS         1g$ body $   define group 1
               1g           call group 1

OUTPUT         o#text#           literal string
               o0n  o0f          integer, float
               o0b  o0s          boolean, string
               o42  o3.14        integer literal, float literal
               o r 1,6           random integer 1-6
               o0s 2,5           string slice (chars 2-5)
               o##               bare newline
               o#x#,0n,#y#       comma-chain (one newline at end)

INPUT          i0n  i0f     integer, float
               i0b  i0s     boolean, string

STRINGS        0s#a# + 1n      assign with concat
               0s + 42         append integer literal
               0s + r 0,9      append random digit
               2s 1s 3,7       slice chars 3-7 of 1s into 2s
               2s 3,5,#new#    replace chars 3-5 of 2s
               4s 2s 3,5,#new# modify 2s, copy to 4s
               0s=1s 0,4       compare 0s to slice of 1s

INDIRECT       0n@n            n[value of 0n]
               0n@s            s[value of 0n]

RANDOM         r 1,6           random integer in [1, 6]
               r 0n,1n         random integer in [0n, 1n]

COMMENTS       _ any text _    ignored everywhere

MISC           p500            pause 500 ms
               p0n             pause 0n ms
               c               clear screen
               x               end program
               :               separator / loop anchor