Jik Grammar Reference
This document is an implementation-derived grammar reference for Jik. It is based on the current lexer and parser in src/jik/lexer.c and src/jik/parser.c.
It is intended to describe the accepted surface syntax of the language. It is not yet a complete formal language specification, because some forms are resolved later during semantic analysis.
Scope
semantically restricted.
- This grammar describes syntax, not full typing or region semantics.
- Some constructs are context-sensitive. In particular:
Type.Membermay denote enum values or variant tags.x[Tag]may denote variant payload access only after semantic resolution.- allocation suffixes such as
[r]and[.x]are syntactically regular, but
- Newlines are significant between statements and after block headers.
Lexical Structure
Comments
comment ::= "//" { any-char-except-newline }
Identifiers
identifier ::= letter { letter | digit | "_" } | "_"
qualified_identifier ::= identifier [ "::" identifier ]
Notes:
_is a reserved standalone token for the local region.Vec,Dict,Option,Some, andNoneare reserved keywords.- Identifiers are currently ASCII-only.
Literals
integer_literal ::= digit { digit }
float_literal ::= digit { digit } "." digit { digit }
| digit { digit } [ "." digit { digit } ] exponent
exponent ::= ("e" | "E") [ "+" | "-" ] digit { digit }
char_literal ::= "'" char-char "'"
string_literal ::= '"' { string-char } '"'
ml_string ::= '"""' { any-char } '"""'
Notes:
while .5 is not.
- The current lexer requires a float to begin with a digit, so
0.5is valid - Character escapes support
\n,\t,\r,\0,\xNN,\\,\', and\". - Ordinary string literals support
\n,\t,\r,\xNN,\\,\', and\". - Ordinary and multiline string literals may contain UTF-8 text.
- Source files that contain non-ASCII text should be saved as UTF-8.
- Ordinary string literals do not support
\0; Jik strings are currently null-terminated. - String length, indexing, and slicing are byte-based.
Embedded C
embed_block ::= "@embed" "{" identifier "}" newline
{ any-line }
identifier newline?
The closing delimiter must appear alone on its line, apart from trailing whitespace.
Embedded C is copied through to the generated C translation. Non-ASCII text in embedded C therefore remains dependent on the host C compiler's handling of C source encoding.
Build Directives
build_directive ::= platform_directive | path_directive | link_directive
platform_directive ::= "@platform" "(" ( "all" | "windows" | "linux" ) ")" newline
path_directive ::= ( "@includedir" | "@libdir" | "@copy" )
"(" string_literal { "," string_literal } ")" newline
link_directive ::= "@link" "(" string_literal { "," string_literal } ")" newline
Build directives are top-level declarations. Each physical module starts with platform all. @platform(windows) or @platform(linux) selects the platform for subsequent build directives in that module, while @platform(all) returns to platform-independent requirements. Platform state does not cross module boundaries and does not conditionally compile Jik declarations or embedded C.
Relative include directories, library directories, and copied files are resolved from the source file containing the directive. Directives from all reachable modules are applied by build, run, and memchk.
Types
type_desc ::= named_type [ alloc_suffix ]
| "Vec" "[" type_desc "]" [ alloc_suffix ]
| "Dict" "[" type_desc "]" [ alloc_suffix ]
| "Option" "[" type_desc "]" [ alloc_suffix ]
named_type ::= qualified_identifier
Named types include built-in names such as:
intdoubleboolcharStringRegionSite
They also include user-defined struct, enum, and variant names.
Allocation Suffixes
Composite literals and some type descriptions may be followed by an allocation suffix:
alloc_suffix ::= "[" identifier "]"
| "[" "." identifier "]"
Examples:
Point{}[r]
Point{}[.p]
Some{"x"}[r]
None[r]
The parser also accepts the local region value _ as an ordinary expression, but allocation suffix syntax itself expects either id or .id.
Allocation suffix syntax takes precedence immediately after composite literals. As a result, forms like [1, 2][0] are not parsed as direct subscripting of a literal. In the current language, composite temporaries must first be bound to a name before subscripting, member access, mutation, or iteration.
Program Structure
program ::= { newline | use_decl | global_decl | top_decl | variant_decl
| enum_decl | embed_block | build_directive }
Imports
use_decl ::= "use" string_literal [ "as" identifier ]
Globals
global_decl ::= identifier ":=" expr
Top-level typed globals such as x: int are not parsed directly. The current parser accepts only top-level := declarations.
Top-Level Declarations
top_decl ::= decl_prefix ( func_decl | struct_decl )
decl_prefix ::= { "extern" | "throws" | "init" }
Current parser restrictions:
struct return type.
externandthrowsmay prefixfunc.extern init func ...declares the default initializer for an opaque externinitis valid only withextern func.externmay prefixstruct.throws struct ...is rejected.
Functions
func_decl ::= "func" identifier "(" [ params ] ")" [ "->" type_desc ] ":"
newline block "end"
Extern functions use a different form:
extern_func_decl ::= "extern" [ "init" | "throws" ] "func"
identifier "as" identifier
"(" [ extern_params ] ")"
"->" type_desc
Parameters
params ::= param { "," param }
param ::= [ "foreign" ] identifier [ ":" type_desc ]
extern_params ::= extern_param { "," extern_param }
extern_param ::= [ "foreign" ] identifier ":" type_desc
Notes:
- Ordinary function parameters may omit type annotations.
- Extern function parameters must have type annotations.
Structs
struct_decl ::= "struct" identifier ":" newline
{ identifier ":" type_desc newline }
"end"
extern_struct_decl ::= "extern" "struct" identifier "as" identifier
Variants
variant_decl ::= "variant" identifier ":" newline
{ identifier ":" type_desc newline }
"end"
Enums
enum_decl ::= "enum" identifier ":" newline
{ identifier newline }
"end"
Blocks and Statements
block ::= { statement newline }
The current parser expects at least one newline after each statement in a block.
Statements
statement ::= return_stmt
| if_stmt
| match_stmt
| while_stmt
| for_stmt
| break_stmt
| continue_stmt
| try_stmt
| decl_stmt
| assign_stmt
| call_stmt
| must_call_stmt
return_stmt ::= "return" [ expr ]
break_stmt ::= "break"
continue_stmt ::= "continue"
decl_stmt ::= identifier ":=" expr
| identifier ":" type_desc
assign_stmt ::= lvalue assign_op expr
assign_op ::= "=" | "+=" | "-=" | "*=" | "/="
call_stmt ::= call_expr
must_call_stmt ::= "must" call_expr
Valid lvalues in the parser are:
lvalue ::= identifier
| primary "." identifier
| primary "[" expr "]"
If
if_stmt ::= "if" expr ":" newline block
{ "elif" expr ":" newline block }
[ "else" ":" newline block ]
"end"
While
while_stmt ::= "while" expr ":" newline block "end"
For
for_stmt ::= "for" identifier "=" expr "," expr ":" newline block "end"
| "for" identifier "in" expr ":" newline block "end"
| "for" identifier "," identifier "in" expr ":" newline block "end"
Try / Except
try_stmt ::= "try" ( call_expr | identifier ":=" call_expr )
":" newline block
"except" ":" newline block
"end"
The parser currently permits only:
- a direct call, or
- a declaration initialized by a direct call
after try.
Match
match_stmt ::= "match" expr ":" newline
{ case_clause }
"end"
case_clause ::= "case" variant_pattern ":" newline block
variant_pattern ::= qualified_identifier "." identifier "{" identifier "}"
Current implementation note:
case Value.TAG{x}:is the supported pattern form.- The parser expects a bound identifier payload in the case pattern.
Expressions
The parser uses the following precedence, from lowest to highest.
Ternary
expr ::= ternary
ternary ::= logical_or [ "if" expr "else" ternary ]
This yields expressions such as:
2 if x == 3 else 12
Logical Operators
logical_or ::= logical_and { "or" logical_and }
logical_and ::= is_expr { "and" is_expr }
is
is_expr ::= comparison [ "is" is_target ]
is_target ::= "Some"
| "None"
| qualified_identifier "." identifier
Examples:
x is Some
x is None
v is Value.INT
Comparison and Arithmetic
comparison ::= term { ("==" | "!=" | "<" | ">" | "<=" | ">=") term }
term ::= factor { ("+" | "-") factor }
factor ::= unary { ("*" | "/" | "%") unary }
unary ::= ( "not" | "-" | "must" ) unary | primary
Primary Expressions
primary ::= atom { postfix }
postfix ::= "." identifier
| "[" expr "]"
| "?"
Examples:
point.x
values[i]
opt?
v[Value.INT]
Atoms
atom ::= integer_literal
| float_literal
| char_literal
| "true"
| "false"
| string_literal [ alloc_suffix ]
| ml_string [ alloc_suffix ]
| "Some" "{" expr "}" [ alloc_suffix ]
| "None" [ alloc_suffix ]
| qualified_identifier
| call_expr
| struct_literal [ alloc_suffix ]
| variant_literal [ alloc_suffix ]
| "(" expr ")"
| vector_literal [ alloc_suffix ]
| dict_literal [ alloc_suffix ]
| "." identifier
| "_"
Calls
call_expr ::= qualified_identifier "(" [ args ] ")"
args ::= expr { "," expr }
Special implementation rule:
unwrap(x)is parsed specially and treated as equivalent tox?.
Struct Literals
struct_literal ::= qualified_identifier "{"
[ struct_field { "," struct_field } ]
"}"
struct_field ::= identifier "=" expr
Variant Literals
variant_literal ::= qualified_identifier "." identifier "{"
[ expr ]
"}"
Examples:
Value.INT{3}
Value.TEXT{"hi"}[r]
Value.INT{}
Vector Literals
vector_literal ::= "[" expr "of" expr "]"
| "[" expr { "," expr } "]"
Examples:
[10 of 0]
[1, 2, 3]
Dictionary Literals
dict_literal ::= "{"
[ dict_entry { "," dict_entry } [ "," ] ]
"}"
dict_entry ::= expr ":" expr
Keys are checked semantically and must be of type String.
Semantic Resolution Notes
These forms are parsed first and refined later:
enum value during semantic analysis.
variant tag marker during semantic analysis.
variant payload access only after semantic resolution.
EnumName.VALUEis initially parsed as member access, then rewritten as anVariantName.TAGis initially parsed as member access, then rewritten as avalue[VariantName.TAG]is syntactically a subscript expression and becomes
Current Implementation Gaps and Constraints
are not part of the current parser.
- Top-level typed globals are not currently parsed.
matchsupports the currentcase Variant.Tag{name}:form; richer patterns