grk_debug.exe — GRK interpreter (Windows x86, debug build) | grk_debug.apk — GRK interpreter (Android, debug build)
Example programs: [programs/]
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:
A GRK program is a sequence of ASCII characters. Whitespace (spaces, tabs, newlines) is ignored everywhere except inside string literals.
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.
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.
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.
GRK has four variable types. Every variable is identified by a numeric index and a single-character type tag.
| Type tag | C type (desktop) | Description |
|---|---|---|
n | int32_t | Signed integer |
f | float | IEEE 754 single (or Q8.8 fixed-point on AVR) |
b | bit in uint8_t | Boolean (y = true, n = false) |
s | char[] | 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.
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
Operators for integer (n) and float (f) variables:
| Operator | Meaning | Precedence |
|---|---|---|
* | Multiply | High |
/ | Divide | High |
% | Modulo | High |
+ | Add | Low |
- | Subtract | Low |
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
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
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 :.
| Operator | Meaning |
|---|---|
= | 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
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.
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# :
ll 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.
lglg 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.
lv Nlv 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 are GRK's functions. They are identified by an integer index and the type tag g.
<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# $
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.
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
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
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.
The i command reads one line of input and stores the parsed result in a variable.
| Form | Reads |
|---|---|
i0n | Signed integer into integer 0 |
i1f | Decimal float into float 1 |
i2b | 1/y/t = true, 0/n/f = false |
i3s | Raw 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.).
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>.
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
Replace characters [start, end] of a string slot with a literal:
2s 1,3,#ord# replace characters 1-3 of 2s with "ord"
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
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
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
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.
| Instruction | Meaning |
|---|---|
x | End program; also usable mid-program |
: | Separator -- ends a conditional body, loop anchor |
; | Else marker -- immediately follows : to start an else block |
c | Clear the output display (platform-dependent) |
p<ms> | Pause for ms milliseconds (literal integer) |
p<idx>n | Pause 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.
All memory sizing is set at compile time by selecting a profile in grk_config.h.
| Profile | Integer | Float | Booleans | Strings | Stack | Program |
|---|---|---|---|---|---|---|
GRK_PROFILE_ATTINY85 | 16 × int16 | 4 × Q8.8 | 16 bits | 3 × 8 chars | 4 | 256 B |
GRK_PROFILE_UNO | 32 × int16 | 8 × Q8.8 | 32 bits | 16 × 16 chars | 8 | 1 KB |
GRK_PROFILE_DESKTOP | 64 × int32 | 64 × float | 64 bits | 100 × 128 chars | 64 | 32 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.
The program below exercises every major feature of the language in a single cohesive program. Annotated sections explain which feature each part demonstrates.
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.
=== 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)
[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)#
$
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)#
$
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