Core
The shared k/q core: evaluation, lexis, types, verbs, adverbs, functions
Required One chapter of the Open Q Language specification. README.txt states what must be implemented, how conformance is defined, and how rule IDs work.
This chapter specifies the core that the k and q surface dialects share: the evaluation model, lexical grammar, type system, verbs, adverbs, functions, and control flow. The q surface adds keywords and pattern matching (q.txt) and the query sublanguage (qsql.txt); neither is repeated here.
CORE-00011. Evaluation model
Right to left, NO operator precedence. A verb applies to the whole expression on its right. Parentheses and brackets are the only grouping.
2*1+1 is 2*(1+1) = 4 not (2*1)+1
5=2+3 is 5=(2+3) = 1b not (5=2)+3
Evaluation is strictly eager: every subexpression yields a concrete value at once. There are no lazy plans. The one lazy construct is the conditional $[c;t;f] (section 9.7), where only the taken branch runs.
Verbs are OVERLOADED BY ARITY, resolved at apply time, not by the lexer:
monadic one argument, prefix -x ,x #x dyadic two arguments, infix x-y x,y x#y
A term's role (noun / verb / adverb) is lexical; its arity (monad vs dyad) is decided by how many operands reach it during evaluation. The parser therefore builds an arity-agnostic tree and defers monad/dyad selection to the evaluator (see Train, section 9.9).
CORE-00022. Dialect: k versus q
One shared core is parsed in two surface dialects, selected per line:
q)expr parse this line as q k)expr parse this line as k
A .k file loads entirely as k. A script is split into maximal same-dialect runs.
k is the terse layer. Relative to q it:
- DROPS the q-only surfaces: qSQL templates (select/exec/update/delete), q 4.1 pattern-match forms, and word-spelled infix keywords.
- ADDS k's defining rule: a bare glyph in monadic position is that glyph's MONADIC OVERLOAD. #x is count, !x is til/key, $x is string, |x is reverse. (section 9.10). v: is the explicit spelling of a verb's monad as a value.
Everything else is the common core and is identical in both dialects: literals, lambdas, adverbs, brackets, $[c;t;f], if/do/while, assignment and amend.
In k mode the word-spelled infix verbs of q are demoted back to plain identifiers: mod xbar and or become names; and/or/mmu do NOT lex to & | $. div and xexp remain infix (they are k-native).
Out of scope for the k surface (q-only): qSQL queries, the pattern conditional :[value;pat;res;...], and the keyword-to-verb rewrite tables. A k parser need not implement these.
CORE-00033. Lexical grammar
The grammar is scannable by a hand-written byte scanner; no regex or tables are needed. Newlines are significant and are emitted as tokens.
CORE-00043.1 Whitespace, comments, newlines
- Space and tab separate tokens.
- A newline is a STATEMENT SEPARATOR at the top level. Inside brackets ( ) [ ] { } the parser tracks depth and treats a newline as whitespace. (Exception: in k mode a newline inside ( ) separates list items like ; .)
- A / that starts a token (preceded by whitespace or line start) begins a comment to end of line. A / adjacent to a noun is the over adverb, not a comment.
- A line that is a single / opens a block comment; a single \ closes it.
- Trailing whitespace and blank lines are insignificant.
CORE-00053.2 Identifiers
A letter or . followed by letters, digits, . and _ . Names may be dotted (namespaced): .cq.load , .Q.fs . A leading . means the global/root namespace. Named builtins (count, sum, all, ...) lex as identifiers, NOT as verbs; they resolve to functions at evaluation time.
CORE-00063.3 Numeric atoms and their suffixes
Default integer literal is LONG (64-bit). Default decimal is FLOAT (64-bit). Suffix letters set the type:
b boolean / bit 1b 0b 0110b
x byte (hex) 0x1f (also vectors, below)
h short (i16) 42h
i int (i32) 42i
j long (i64) 42j (same as bare 42)
e real (f32) 3.14e 1e
f float (f64) 3.14f 20. (bare 3.14 is already float)
Scientific notation uses lowercase e only: 1e9 is float. Uppercase 1E3 is an error. A leading - before a numeric literal is a sign, not the subtract verb, when it is immediately followed by a digit or a null/inf marker.
CORE-00073.4 Nulls and infinities (case sensitive)
Null marker 0N , infinity marker 0W (uppercase). Float forms use lowercase 0n / 0w . A type suffix selects width:
type null +inf null sentinel +inf sentinel
short 0Nh 0Wh i16::MIN i16::MAX
int 0Ni 0Wi i32::MIN i32::MAX
long 0N 0Nj 0W 0Wj i64::MIN i64::MAX
real 0Ne 0We f32 NaN f32 +Infinity
float 0n 0Nf 0w 0Wf f64 NaN f64 +Infinity
guid 0Ng (none) all-zero bytes --
Bare 0N / 0W default to LONG. Bare 0n / 0w default to FLOAT. -0w / -0Wi etc. are negative infinity (the leading - is a sign). boolean, byte, and char have NO null and NO infinity (as in kdb).
CORE-00083.5 Byte and bit literals
Byte atom: 0x1f one hex pair
Byte vector: 0x0102 , 0x a run of hex pairs, or empty
Bit atom: 1b boolean true
Bit vector: 0110b four booleans; a trailing b over a digit run
makes the whole run boolean (0 0 1b)
CORE-00093.6 Temporal literals
THE EPOCH IS 2000.01.01. Every absolute temporal value counts from midnight on that date, in its own unit, as a signed integer; earlier instants are negative. "j"$2000.01.01 is 0 and "j"$1970.01.01 is -10957. An implementation that stores temporals on the Unix epoch must still cast, compare and display them as if the origin were 2000.01.01.
kind code unit literal
timestamp 12 ns since epoch 2026.06.12D12:00:00.000000000
month 13 months since epoch 2026.06m
date 14 days since epoch 2026.06.12
datetime 15 days since epoch, f64 2026.06.12T12:00:00.000
timespan 16 nanoseconds (DURATION) 0D01:30:00.000000000
minute 17 minutes since midnight 12:00
second 18 seconds since midnight 12:00:00
time 19 ms since midnight 12:00:00.000
timespan, minute, second and time are durations or times-of-day: they have no epoch. Only timestamp, month, date and datetime are absolute.
Disambiguation: Y.M.D form: trailing D -> timestamp, trailing T -> datetime, neither ->
date. (datetime is the one float-backed temporal: 2000.01.02T06:00:00
is 1.25. It is deprecated but not removed.)
HH:MM family, by structure: 2 fields -> minute; 3 fields -> second;
3 fields + .fff (<=3 fraction digits) -> time; MORE than 3 fraction
digits promotes to timespan (12:00:00.123456 is a timespan).
Suffix coercion t / v / u rescales a bare HH:MM... to time / second /
minute respectively.
Temporal nulls: 0N plus a kind suffix -> i64::MIN. Suffix letters:
d date m month p timestamp n timespan
t time v second u minute z datetime
Temporal infinities: 0W plus a kind suffix -> i64::MAX, for every kind above (0Wd 0Wm 0Wp 0Wn 0Wt 0Wu 0Wv). 0Wz is float-backed and displays lowercase as 0wz. There is NO 0Wg: guid has a null but no infinity.
CORE-00103.7 Characters and strings
Double quotes delimit a char vector: "hello". Escapes \" \\ \n \t \r and \NNN octal. A ONE-character string "a" evaluates to a CHAR atom (type -10); a multi-character string is a CHAR VECTOR (type 10). A string is exactly a list of chars: "foo" ~ ("f";"o";"o") is 1b.
CORE-00113.8 Symbols
Backtick then an identifier-ish body: `ibm . The backtick is not part of the value. An empty symbol is a lone backtick: ` . A run of adjacent symbols is a symbol vector: `a`b`c . Symbols are interned. File / IPC paths are symbols beginning with a colon:
`:path/to/file local file path
`:host:port IPC endpoint
CORE-00123.9 Glyph verbs
Only these glyphs are verbs. Everything word-spelled is an identifier.
+ - * % = <> < > <= >= , ! # _ ^ @ . & | ~ ? $
Keyword-spelled infix verbs (dyadic only, no monad), still verbs:
div mod xexp xbar in
(In k mode: div and xexp stay verbs; mod xbar and or in become plain identifiers per section 2.) File verbs:
0: text (parse/format/write delimited text)
1: binary (fixed-width read; raw byte write)
The colon as a value:
: identity monad; as a dyad-value it is "replace" (returns its right
argument), used inside amend @[x;i;:;y].
CORE-00133.10 Adverbs (iterators)
' each ': each-prior / over (fold / reduce) \ scan \: each-left /: each-right
CORE-00143.11 Punctuation tokens
: ; { } [ ] ( ) and Newline
CORE-00153.12 System command
A line beginning with \ (not a block-comment close) is a system command: \cmd args... (the leading \ is not part of the command name).
CORE-00163.13 Vector runs (a lexer/parser coalescing rule, key for a fast parser)
Adjacent same-family literals separated only by spaces coalesce into ONE typed vector node at parse time; no ( ; ) needed:
1 2 3 long vector
1 2 3h short vector (a suffix anywhere sets the run type)
0x01 0x02 == 0x0102 byte vector
`a`b`c symbol vector
0 0 1b bool vector
2026.01.01 2026.01.02 date vector
Type promotion within a numeric run: float > real > int > short > long; the widest present wins, and null/inf sentinels remap to that width (0N 2 3i becomes 0Ni 2 3i). An incompatible element ends the run. This is why a simple k parser scans for a maximal literal run before falling back to general expression parsing.
CORE-00174. Types (the runtime value representation)
type x returns the short type code: a vector is the positive code, an atom the negative. Code 3 is unused. Codes 20h and above are enumerations (section 4.6).
CORE-00184.1 Atoms
code char type variant literal -1 b boolean Bool 1b 0b -2 g guid Guid 0Ng, "G"$"...", n?0Ng -4 x byte Byte 0x1f -5 h short Short 42h -6 i int Int 42i -7 j long Long 42 42j -8 e real Real 3.14e -9 f float Float 3.14 3.14f -10 c char Char "a" -11 s symbol Symbol `ibm -12 p timestamp Temporal 2026.06.12D12:00:00.000000000 -13 m month Temporal 2026.06m -14 d date Temporal 2026.06.12 -15 z datetime Temporal 2026.06.12T12:00:00.000 -16 n timespan Temporal 0D01:30:00.000000000 -17 u minute Temporal 12:00 -18 v second Temporal 12:00:00 -19 t time Temporal 12:00:00.000
CORE-00194.2 Typed (simple, homogeneous) vectors
The positive of the atom code. Each has a dedicated Value variant: BoolVec GuidVec ByteVec ShortVec IntVec LongVec RealVec FloatVec CharVec (a char vector is a string). A simple vector is homogeneous; appending a value of another type promotes it to a general list (section 4.3).
CORE-00204.3 General (mixed) list
type code 0: a list whose elements may be of any type. Written with ( ; ):
(1; 2.5; "foo"; `sym)
Empty list: () Single element (expr) is GROUPING, not a one-list. To enlist one item use ,x or (x;). A HOLE anywhere makes it an enlist PROJECTION, not a list: (1;;3) , (a;b;) . A nested list of vectors is how MATRICES are represented (section 4.7).
CORE-00214.4 Dictionary
type code 99. No dict literal; built with ! :
`a`b`c!1 2 3
keys and values are each any value (usually two conforming vectors).
CORE-00224.5 Table
type code 98: named, equal-length column vectors (a flipped column-dictionary). Table literal:
([] name:col; name:col; ...) plain table
([keyname:col] name:col; ...) keyed table
flip (the monad of +) transposes a column dict into a table. A keyed table is a dictionary whose keys and values are BOTH tables, and reads as type 99, not 98.
CORE-00234.6 Foreign keys / enums
`d$`a`b enumerates symbols against the symbol vector named d, yielding an enumeration: type 20h for the first domain, 21h for the next, and so on. value recovers the symbols; the stored representation is an int index into the domain. An enumerated column is how a symbol column is held on disk (storage.txt). A foreign key is an enumeration whose domain is a keyed table's key column.
CORE-00244.7 Matrices / nested types
No dedicated matrix type. A matrix is a general list whose elements are equal-length vectors (rectangular by convention only). Indexing supports an elided axis: mat[;1] selects column 1 across every row. Any list/vector may nest a list/vector to arbitrary depth; type applies at each level.
CORE-00254.8 Function and singleton values (each has a type code)
100 lambda {[x;y] x+y}
101 verb monad, and unit (::) #: -: .: ::
102 verb / builtin as a value (+)
103 iterator as a value (') (/)
104 projection +[2]
105 composition '[not;null]
106 each f'
107 over f/
108 scan f\
109 each-prior f':
110 each-right f/:
111 each-left f\:
112 dynamic load 2:
Each iterator gets its OWN code (106-111); they are not one "derived" code. 101 is shared by a verb's monad and by unit.
CORE-00264.9 Null and infinity summary
Integer nulls are sentinel min-values; integer +inf are sentinel max-values. Float null is NaN, float inf is IEEE infinity. Temporal null is i64::MIN. boolean/byte/char have no null. See section 3.4 for literals.
CORE-00275. Verbs — monadic and dyadic meanings
Each glyph is overloaded: the monad (prefix, one arg) and the dyad (infix, two args) are different operations. In k, the bare glyph in monadic position IS the monad (section 9.10). In q you usually spell the monad with a keyword.
glyph monadic dyadic
+ flip / transpose add
- negate subtract
* first multiply
% reciprocal divide (result is always float)
= group (value -> indices) equal (loose: 42=42.0 is 1b)
<> -- not equal
< grade up (iasc) for lists; less than
hopen for a `:target symbol
> grade down (idesc) for lists; greater than
hclose for a handle
<= -- less than or equal
>= -- greater than or equal
, enlist (wrap atom in a list) join / concatenate; tables append,
keyed tables upsert
! til (for an int) / key (of a make dict keys!values; n!table keys
dict or namespace) by first n cols; 0!kt unkeys.
Reserved codes: 0N! -3! display;
-8! -9! -11! -21! -22! serialize
# count take / reshape (cycling; negative =
tail). Symbol left = set attribute
`s# `u# `p# `g# ; the attribute
PERSISTS and attr reports it
_ floor drop / cut (negative = drop tail);
`k _ dict drops keys
^ null (is-null predicate) fill (replace nulls in y with x)
@ type index-at / apply-at. 3-arg: trap
@[f;x;e] or amend @[x;i;f]. 4-arg:
amend-with-value @[x;i;f;y]
. value / eval (symbol gets; index-at-depth / apply f . args.
string evals; list applies; 3/4-arg: trap or amend at depth
dict -> values)
& where (mask -> indices) min / and (boolean stays boolean)
| reverse max / or
~ not match: structural equality, TYPE
INCLUDED (1.0~1 is 0b). Floats
compare tolerantly: values within
2^-43 relative are equal, so
1.0~1+2 xexp -44 is 1b but
1.0~1+2 xexp -43 is 0b
? distinct find (first index). int-left: roll /
deal. 3-arg: vector conditional
?[mask;x;y]. 4/5-arg: functional
select / exec (q surface)
$ string cast `kind$v ; also matrix multiply
(mmu); $[c;t;f;...] scalar cond
when 3+ args
: identity (returns its arg) replace (returns y) -- used in amend
Keyword-spelled dyadic verbs (no monad):
div integer divide (floor; result type follows the left operand)
mod modulo (floor; result takes the divisor's sign)
xexp power (float): 2 xexp 4 is 16
xbar bucket down to a multiple of n
in membership: x in y -> boolean
0: delimited text: parse / format / write
1: fixed-width binary read; raw byte write
Traps for an implementer:
- % is ALWAYS float: 35%5 is 7f. Use div for integer division.
- mmu is float-only. Integer operands are a 'type error, NOT promoted: 1 2 mmu 1 2 signals type. Cast first.
- Attributes are real state, not a hint. `s#1 2 3 sets `s and attr reports it; asc sets `s as a side effect. An implementation that validates the attribute but discards the flag does not conform.
- Match ~ is type-strict but float-tolerant; equality = is type-loose (42=42.0 is 1b) and equally tolerant.
CORE-00286. Adverbs / iterators
An adverb modifies a verb or function, producing a DERIVED function. It binds to the term on its LEFT.
glyph name behaviour
' each monadic: map f over items. dyadic: zip f over pairs.
': each-prior apply f to each adjacent pair (x_i, x_i-1).
/ over fold/reduce to one result. With a seed: x f/ y.
4-arg over of @ or . is an iterated amend.
\ scan like over but returns every intermediate result.
\: each-left fix the right arg, vary the left over its items.
/: each-right fix the left arg, vary the right over its items.
Noun overloads: when the LEFT operand of \: or /: is a NOUN (not a function),
x \: is the verb vs (split / decode)
x /: is the verb sv (join / encode)
Common derived words built from adverb+verb (available as names):
sums +\ prds *\ maxs |\ mins &\ fills ^\
raze ,/ deltas -': ratios %':
prev is :': and next is the same each-prior over the reversed list.
CORE-00297. Identifiers, builtins, namespaces
Named builtins are ordinary identifiers resolved at eval time (the lexer does not special-case them). The library is large: aggregates (count sum avg min max first last distinct ...), list ops (reverse rotate raze sublist ...), ordering and search (asc desc iasc idesc rank bin group where ...), math (abs signum sqrt exp log sin cos ... and or), stats and moving windows (var dev med wavg mavg msum mcount ema ...), text (lower upper trim like ss ssr md5 ...), dict/table (key value cols flip meta xasc xkey xgroup insert upsert ...), joins (lj ij uj ej aj wj pj asof fby ...), reflection/system (parse eval value get set save load system exit ...), IO/IPC/JSON (hopen hclose read0 read1 ...).
Namespaces are dotted: a name may live under .Q.* .z.* .j.* or any user or implementation namespace (runtime.txt). A leading . anchors at root. A dotted name in a query keeps its last segment as the column name.
A k parser does not need to know the builtin set: unknown identifiers are just name references resolved later. Only glyph verbs, keyword verbs, and adverbs are syntactically special.
CORE-00308. Lists, dictionaries, tables — syntactic summary
() empty general list
(a; b; c) general list of three expressions
(a) grouping, NOT a list
,a enlist a (one-element list)
1 2 3 typed vector (run coalescing, section 3.13)
`a`b`c symbol vector
"abc" char vector (string)
`a`b!1 2 dictionary (! operator)
([] a:1 2; b:3 4) table
([k:1 2] v:3 4) keyed table
(1 2;3 4;5 6) nested list (a matrix by convention)
Indexing:
x[i] index / apply x[i;j] index at depth 2
x[;j] elided first axis (all rows, column j)
x . (i;j) index at path
x[i]:v indexed amend (see 9.4)
CORE-00319. Syntactic forms (what the parser builds)
The parser is precedence-free recursive descent, right to left. Its AST is the practical grammar; the main node kinds follow.
CORE-00329.1 Application and indexing
head[a;b;...] applies head to the bracketed args. Juxtaposition head arg is also application. Indexing a list/dict/table uses the same bracket form. f . args applies with an argument LIST. f@x is monadic apply (same as f x).
CORE-00339.2 Projection and elision
Supplying fewer args than a function's rank, or leaving a bracket slot empty, yields a PROJECTION (a partially applied function):
f[;2] project on the first argument
(+)[2] project the dyad + to add-2 (equivalent to 2+ )
An empty slot is an Elision node. Distinguish it from :: (GenericNull / identity value): @[f;::;e] applies, f[;2] projects.
CORE-00349.3 Assignment
name:expr assign (local inside a lambda, else global) name::expr assign to the GLOBAL binding even inside a lambda
Assignment is parsed INSIDE expr, so it nests and binds the whole rest of the line to its right: c:3+b:2+a:1 assigns a=1, b=3, c=6. A bracket block as a plain : value is rejected: x:[1;2;3] is an error.
CORE-00359.4 Amend assignment
name verb:expr is name: name verb expr r,:x appends x to r name verb::expr amends the GLOBAL binding.
Indexed amend desugars to functional amend on the same name:
name[i]:v -> name:@[name;i;:;v]
name[i;j]:v -> name:.[name;(i;j);:;v]
name[i]op:v -> name:@[name;i;op;v]
These target the existing binding (global if no local), they do not declare.
CORE-00369.5 Return and signal
:expr explicit return from a lambda body (statement position only) 'x signal / throw: raise an error whose text is x (a string or
symbol). (Distinct from the each adverb, which follows a
function term.)
CORE-00379.6 Lambdas
{expr} implicit params x, y, z (up to 8: x y z then more)
{[a;b] expr; expr} explicit params; body is ;-separated statements,
value is the last statement
{[] expr} niladic (no args)
A lambda's source text is retained so it can be re-emitted (e.g. serialized over IPC as kdb type 100). Free names resolve to enclosing/global scope; local names are resolved to frame slots at parse time for fast access.
Parameter PATTERNS (q 4.1, available in the shared core; a k parser may omit the exotic ones): a param may be a plain Name, a Blank _ , or a destructuring List / Dict pattern. The everyday forms are the plain name and [] .
CORE-00389.7 Conditional (an expression, lazy)
$[c;t;f] if c then t else f $[c1;e1;c2;e2;...;d] multi-way; the first true branch's result; d default
Only the taken branch is evaluated. With fewer than 3 args, $ is cast, not conditional.
CORE-00399.8 Control flow (statements, no value)
if[test; e1; e2; ...] run the body when test is true; no else, no value do[n; e1; e2; ...] run the body n times while[test; e1; e2; ...] run the body while test is true
These are recognized as a leading keyword directly followed by [ .
CORE-00409.9 Blocks and trains
[e1;e2;...;en] a statement block: evaluate each in order in the enclosing
scope, yield the last. Empty [] yields unit (::).
A juxtaposition of 2+ terms is a TRAIN. The evaluator resolves application and infix by each term's ARITY: a dyadic middle term applies infix (count each x), a monadic one applies prefix (count distinct x). This is the core reason parsing stays arity-agnostic and dispatch is deferred.
CORE-00419.10 The k monadic-glyph rule (k's defining feature)
In k, a bare glyph with a single operand on its right is that glyph's MONADIC overload, parsed as a VerbMonad application:
#x count !x til / key $x string
|x reverse &x where *x first
^x null ,x enlist =x group ...
The value form (a monad as a first-class value) is written glyph then colon:
#: count as a value -: negate as a value .: value/eval
Applied forms parse to a Monadic node; the bare v: form parses to VerbMonad.
CORE-004210. Notes for the parser implementer
- Two passes are enough: a byte-scanning lexer, then a precedence-free right-to-left recursive descent. No precedence table, no shunting yard.
- Arity is NOT a parse concern. Build monad/dyad-agnostic nodes (a verb term, its left operand if present, its right operand); let the evaluator pick the overload by counting operands. Only bracket application fixes arity early.
- Coalesce literal runs (section 3.13) in the lexer or a thin pre-pass; it is the single biggest simplifier and the biggest speed win for real k code.
- Track bracket depth so newlines are statement separators only at depth 0.
- Resolve lambda-local names to integer slots at parse time; global/free names stay as name references. This keeps evaluation a slot index, not a hash.
- The lexer must report "incomplete" (unterminated string, unbalanced brackets) distinctly from "malformed" so a REPL can keep reading lines.
- Right-to-left plus assignment-inside-expr means an assignment binds the entire remaining right side; do not special-case it out of the expression grammar.
CORE-004311. Display form
-3! renders a value as text. The result is the K display form and is a distinct, observable part of the language: it is what a console echoes, what a serialized lambda carries, and what error and log output embed.
It is NOT the same as q source. A bare monadic glyph is the monad in k but not in q (section 9.10), so -3! output round-trips through the k parser and not through the q one:
-3!enlist 1 is ,1 not enlist 1
-3!flip `a`b!(1 2;3 4) is +`a`b!(1 2;3 4)
value "k)+`a`b!(1 2;3 4)" works
value "+`a`b!(1 2;3 4)" signals
An implementation MUST render the K form: enlist 1 renders as ,1 and a table renders as +dict. An implementation that renders q source instead does not conform to this section.
Float rendering depends on the display precision \P; value equality does not. -3!sqrt 2 and string 3.14 are therefore specified at the default precision \P 7. Every other rule in this specification is precision-independent.
END
Source: spec/core.txt