mirror of
https://github.com/boolean-maybe/tiki
synced 2026-04-21 13:37:20 +00:00
6.5 KiB
6.5 KiB
Syntax
Table of contents
- Overview
- Lexical structure
- Top-level grammar
- Condition grammar
- Expression grammar
- Operator binding summary
- Syntax notes
Overview
This page describes ruki syntax. It starts with tokens and then shows the grammar for statements, triggers, conditions, and expressions.
Lexical structure
ruki uses these token classes:
- comments:
--to end of line - whitespace: ignored between tokens
- durations:
\d+(sec|min|hour|day|week|month|year)s? - dates:
YYYY-MM-DD - integers: decimal digits only
- strings: double-quoted strings with backslash escapes
- comparison operators:
=,!=,<,>,<=,>= - binary operators:
+,- - star:
* - punctuation:
.,(,),[,],, - identifiers:
[a-zA-Z_][a-zA-Z0-9_]*
Examples:
-- line comment
2026-03-25
2day
"hello"
dependsOn
new.status
Top-level grammar
The following EBNF-style summary shows the grammar:
statement = selectStmt | createStmt | updateStmt | deleteStmt ;
selectStmt = "select" [ fieldList | "*" ] [ "where" condition ] [ orderBy ] [ "limit" int ] [ pipeAction ] ;
fieldList = identifier { "," identifier } ;
pipeAction = "|" ( runAction | clipboardAction ) ;
clipboardAction = "clipboard" "(" ")" ;
createStmt = "create" assignment { assignment } ;
orderBy = "order" "by" sortField { "," sortField } ;
sortField = identifier [ "asc" | "desc" ] ;
updateStmt = "update" "where" condition "set" assignment { assignment } ;
deleteStmt = "delete" "where" condition ;
assignment = identifier "=" expr ;
trigger = timing event [ "where" condition ] ( action | deny ) ;
timing = "before" | "after" ;
event = "create" | "update" | "delete" ;
action = runAction | createStmt | updateStmt | deleteStmt ;
runAction = "run" "(" expr ")" ;
deny = "deny" string ;
timeTrigger = "every" duration ( createStmt | updateStmt | deleteStmt ) ;
Notes:
selectis a valid top-level statement, but it is not valid as a trigger action.createrequires at least one assignment.updaterequires bothwhereandset.deleterequireswhere.order byis only valid onselect, not on subqueries insidecount(...).limittruncates the result set to at most N rows, applied after filtering and sorting but before any pipe action.asc,desc,order,by, andlimitare contextual keywords — they are only special in the SELECT clause.- Bare
selectandselect *both mean all fields. A field list likeselect title, statusprojects only the named fields. everywraps a CRUD statement with a periodic interval. Onlycreate,update, anddeleteare allowed
Condition grammar
Condition precedence follows this order:
condition = orCond ;
orCond = andCond { "or" andCond } ;
andCond = notCond { "and" notCond } ;
notCond = "not" notCond | primaryCond ;
primaryCond = "(" condition ")" | exprCond ;
exprCond = expr
[ compareTail
| isEmptyTail
| isNotEmptyTail
| notInTail
| inTail
| anyTail
| allTail ] ;
compareTail = compareOp expr ;
isEmptyTail = "is" "empty" ;
isNotEmptyTail = "is" "not" "empty" ;
inTail = "in" expr ;
notInTail = "not" "in" expr ;
anyTail = "any" primaryCond ;
allTail = "all" primaryCond ;
Examples:
select where status = "done"
select where assignee is empty
select where status not in ["done", "cancelled"]
select where dependsOn any status != "done"
select where not (status = "done" or priority = 1)
Field list:
select title, status
select id, title where status = "done"
select * where priority <= 2
select title, status where "bug" in tags order by priority
Order by:
select order by priority
select where status = "done" order by updatedAt desc
select where "bug" in tags order by priority asc, createdAt desc
Limit:
select order by priority limit 3
select where status != "done" order by priority limit 5
select limit 1
Expression grammar
Expressions support literals, field references, qualifiers, function calls, list literals, parenthesized expressions, subqueries, and left-associative + or - chains:
expr = unaryExpr { ("+" | "-") unaryExpr } ;
unaryExpr = funcCall
| subQuery
| qualifiedRef
| listLiteral
| string
| date
| duration
| int
| emptyLiteral
| fieldRef
| "(" expr ")" ;
funcCall = identifier "(" [ expr { "," expr } ] ")" ;
subQuery = "select" [ "where" condition ] ;
qualifiedRef = ( "old" | "new" ) "." identifier ;
listLiteral = "[" [ expr { "," expr } ] "]" ;
emptyLiteral = "empty" ;
fieldRef = identifier ;
Examples:
title
old.status
["bug", "frontend"]
next_date(recurrence)
count(select where status = "done")
2026-03-25 + 2day
tags + ["needs-triage"]
Operator binding summary
Condition operators:
- highest: a condition in parentheses, or a condition built from a single expression
- then:
not - then:
and - lowest:
or
Expression operators:
- only one binary precedence level exists for expressions
+and-associate left to right
That means:
select where priority = 1 or priority = 2 and status = "done"
parses as:
priority = 1 or (priority = 2 and status = "done")
Syntax notes
anyandallapply to the condition that comes right after them. If you want to combine that condition withandoror, use parentheses.selectused inside expressions is only valid as acount(...)argument. Bare subqueries are rejected during validation.- The grammar accepts
run(<expr>), but only as the top-level action of anaftertrigger. old.andnew.are only allowed in some trigger conditions. See Semantics and Validation And Errors.