Writing Karaka

A tutorial that walks from your first program to pipeline-based error handling. Each section references an example in examples/ so you can run full programs as you read.

First program

Create namaste.karaka:

प्रारम्भः
  लिख("नमस्ते, लोक!")
  नाम = "कारक"
  लिख("भाषायाः नाम: {नाम}")
समाप्त

Run it:

karaka run namaste.karaka

Output:

नमस्ते, लोक!
भाषायाः नाम: कारक

Every runnable program has exactly one प्रारम्भः block. Statements inside execute top to bottom. लिख prints one line; it takes exactly one argument.

See examples/01-namaste.karaka.

The REPL

karaka repl
कारक> लिख("नमस्ते")
नमस्ते
कारक> चल  = ;  =  + ; 
1

The REPL keeps globals alive across inputs. Semicolons separate statements on a single line. Type :त्यज or :quit to exit. शून्यम् values are not echoed back.

Typing Devanagari

Karaka source files are plain UTF-8 text. You can type Devanagari in any editor, terminal, or the REPL once you have an input method configured at the OS level. That is the recommended path — it works everywhere without any plugin and lets you use the full script correctly.

OS input method setup

macOS

  1. Open System Settings > Keyboard > Input Sources, click +.
  2. Find Hindi and select Hindi — Transliteration.
  3. Switch to it with the menu bar flag or Ctrl+Space.
  4. Type phonetically: namaste produces नमस्ते, karma produces कर्म. The macOS scheme is close to ITRANS; most words work as you expect.

Windows

  1. Settings > Time & Language > Language > Add a language > Hindi.
  2. Choose the Hindi Phonetic keyboard (Microsoft Indic Language Input Tool), or install Microsoft Indic Input 3 for more coverage.
  3. Switch with Windows+Space.

Linux

Install ibus-m17n and enable the hi-itrans input method:

sudo apt install ibus-m17n   # Debian / Ubuntu
ibus-setup                   # add hi (m17n) → hi-itrans

Mobile (Android / iOS)

Install Gboard, add Hindi as a language, and use the transliteration keyboard. Type phonetically; Gboard offers candidate selection.

karaka translit workflow

The fastest way to write a new Karaka program is to draft it in Roman, then convert it in one step:

karaka translit roman-draft.txt > program.karaka
karaka run program.karaka

karaka translit reads a file (or stdin with -) and writes Devanagari to stdout. Conversion is word-wise: keyword dictionary first (case-insensitive), ITRANS engine for everything else. Words that are already Devanagari pass through unchanged.

String literals and comments are preserved by default. The content of "...", '...', line comments (# …), and block comments (#= … =#) is not transliterated unless you add --sarvam:

karaka translit roman-draft.txt --sarvam > program.karaka

Use --sarvam when your string values should also be Devanagari. The default behaviour is intentional: a string like "admin" is usually an API value, not a Sanskrit word, and should stay Roman.

VS Code extension flow

With the vscode-karaka extension installed, you do not need an OS input method for common keywords. As you type a Latin run in a .karaka file the completion popup shows:

The command Karaka: Transliterate word at cursor (Ctrl+Shift+PKaraka: Transliterate) converts the Latin word under the cursor in place. Keyword table wins on an exact match; otherwise the ITRANS engine is used.

The extension also ships code snippets for the most common constructs. Type the prefix and press Tab to expand:

PrefixExpands to
kriyaRole-declared क्रिया skeleton with अपेक्षते and फलम्
niyamaनियमः skeleton with प्रमाणम् + अनुमतम्/निषिद्धम् branches
jatiजातिः with two गुणः fields
yadiयदि / अन्यथा / समाप्त conditional block
prarambhaप्रारम्भः entry block
pratyekamप्रत्येकम् loop over a range or list

Playground toggle

The browser playground at site/playground/ has an अनुवर्णनम् (type Latin) checkbox in the toolbar. When checked, pressing Space, Enter, or punctuation after a Latin word replaces it with Devanagari automatically. See the playground README for the limitation note.

ITRANS cheat sheet

The scheme used by the VS Code extension and the playground toggle:

Vowels

LatinIndependentMatraExample
a(inherent)karma → कर्म
aa or Akartaa → कर्ता
iिyadi → यदि
ii or Ikriyaa → क्रिया
u
uu or Ushuunyam → शून्यम्
R^i
enamaste → नमस्ते
ai
o
au

Consonants

LatinDevanagariLatinDevanagariLatinDevanagari
kkhg
gh~Nch or c
Ch or chhjjh
~nTTh
DDhN
tthd
dhnp
ph or fbbh
myr
lv or wsh
Sh or Ssh
LkSh or xक्षj~n or GYज्ञ

Marks and digits

LatinOutputName
M or .manusvara
Hvisarga
.Ncandrabindu
.aavagraha
09०–९Devanagari digits
--samasa hyphen (passes through)

Worked examples

Values and bindings

Bindings

A plain assignment creates an immutable binding:

 = 

चल declares a mutable binding:

चल  = 
 =  + 

नित्य explicitly marks immutability and accepts a type annotation:

नित्य गति: संख्या = ९.८

Attempting to reassign a non-चल binding is a runtime error (K2006).

Numeric rules

Integers are i64. Decimals are f64. The annotation संख्या accepts both.

Division / always returns a decimal (दशांशः), even with integer operands:

 /    # → 1.25

Modulo % is integer-only and errors on non-integer operands:

 %    # → 1

Both Devanagari digits (१२३) and ASCII digits (123) are legal and equivalent:

लिख(१२३ == 123)   # → सत्य

A literal may not mix Devanagari and ASCII digits in one token (K0008).

Integer overflow is a runtime error (K2018). Decimal overflow produces अनन्तम् (IEEE 754 infinity); अनन्तम् - अनन्तम् produces असंख्यम् (NaN).

Strings and interpolation

String literals use double quotes. An expression inside {...} is interpolated:

नाम = "अर्जुनः"
लिख("नमस्ते, {नाम}!")   # → नमस्ते, अर्जुनः!

दीर्घता gives the grapheme-cluster length:

वाक्यम् = "क्षेत्रम्"
लिख(वाक्यम्.दीर्घता)   # grapheme clusters, not bytes

Strict truthiness

Conditions require a सत्यता (Bool) value. There is no truthiness coercion. Writing यदि ५ तदा ... is a K2002 type error at runtime.

The boolean constants are सत्य and मिथ्या. Negation uses the keyword :

यदि  ( == ) तदा
  लिख("nonzero")
समाप्त

Control flow

See examples/02-ganana.karaka.

यदि / तदा / अन्यथा

यदि संख्या %  ==   संख्या %  ==  तदा
  लिख("त्रिपञ्चकम्")
अन्यथा यदि संख्या %  ==  तदा
  लिख("त्रिकम्")
अन्यथा यदि संख्या %  ==  तदा
  लिख("पञ्चकम्")
अन्यथा
  लिख("{संख्या}")
समाप्त

तदा is optional when the condition ends a line. The अन्यथा यदि chain must appear on the same line as the closing समाप्त of the previous arm. A newline before यदि nests instead of chains.

यावत्

चल  = 
यावत्  > 
  लिख()
   =  - 
समाप्त

The condition must be सत्यता; any other type is a K2002 error.

प्रत्येकम्

Iterates over a range or a list:

प्रत्येकम् संख्या मध्ये ..१०
  लिख(संख्या)
समाप्त

Ranges are inclusive at both ends. A reversed range (३..१) is empty. List iteration takes a snapshot of the list before the loop begins, so mutations inside the loop do not affect iteration.

Collections

See examples/03-suchi-kosha.karaka.

सूची (list)

चल  = ["", "", ""]

# append — method sugar or explicit role call
.योजनम्(कर्म: "")

# length, first, last
लिख(.दीर्घता)
लिख(.प्रथमः)
लिख(.अन्तिमः)

# 0-based index read
लिख([])

# 0-based index write (requires चल binding)
[] = ""

# membership test
लिख("" मध्ये )   # → सत्य

कोशः (map)

शब्दकोशः = {अर्थः: "meaning", उदाहरणम्: "example"}

# member read by identifier key
लिख(शब्दकोशः.अर्थः)

# index read by string key
लिख(शब्दकोशः["उदाहरणम्"])

# index write adds or updates
चल  = {: }
[""] = 

Container mutability

चल controls whether a binding can be rebound to a different container. The container's interior is always mutable — you can index-assign a list or map regardless of whether the binding is चल. The same rule applies to जातिः instances: चल-declared fields can be reassigned on the instance after construction.

Actions (क्रिया)

See examples/04-kriya.karaka and examples/06-hastantaranam.karaka.

Positional क्रिया

Arguments bind by position, no role labels required:

क्रिया योगः(, )
  फलम्  + 
समाप्त

लिख(योगः(, ))   # → 7

Use positional when the function is a pure computation over values of the same kind (math, string helpers, predicates).

Role-declared क्रिया

Roles name the semantic function of each argument. Declare them in अपेक्षते:

क्रिया हस्तान्तरणम्
  अपेक्षते
    अपादानम्: खातम्        # source (ablative: from)
    सम्प्रदानम्: खातम्      # recipient (dative: to)
    कर्म: संख्या            # amount (accusative)
  यदि अपादानम्.शेषः < कर्म तदा
    दोषः "अपर्याप्तः शेषः"
  समाप्त
  अपादानम्.शेषः = अपादानम्.शेषः - कर्म
  सम्प्रदानम्.शेषः = सम्प्रदानम्.शेषः + कर्म
समाप्त

हस्तान्तरणम्(अपादानम्: खाता१, सम्प्रदानम्: खाता२, कर्म: ५००)

Call sites must use the role labels. This makes direction errors impossible to write silently.

The six कारक

रोलअर्थ
कर्ताthe agent — who acts
कर्मthe patient — what is acted upon
करणम्the instrument — how the action is performed
सम्प्रदानम्the recipient — to whom
अपादानम्the source — from where
अधिकरणम्the locus — where/on which (also the receiver in method calls)

Use roles whenever the क्रिया involves more than one entity or any notion of authority or direction. Roles at every call site mean the intent is readable without looking at the definition.

Optional arguments and defaults

Mark a role optional with ?. Provide a default expression after =:

क्रिया नमनम्
  अपेक्षते
    कर्ता: वाक्यम्
    सम्प्रदानम्: वाक्यम् = "लोक"
  फलम् "नमस्ते, {सम्प्रदानम्}! अहं {कर्ता} अस्मि।"
समाप्त

लिख(नमनम्(कर्ता: "अर्जुनः"))
# → नमस्ते, लोक! अहं अर्जुनः अस्मि।

Default expressions evaluate against the global scope at call time.

Default कर्म

A क्रिया that declares exactly one role labeled कर्म can be called with a single unlabeled argument:

क्रिया द्विगुणम्
  अपेक्षते
    कर्म: संख्या
  फलम् कर्म * 
समाप्त

लिख(द्विगुणम्())         # unlabeled; fills कर्म
लिख(द्विगुणम्(कर्म: ))   # explicit; identical result

Method sugar through अधिकरणम्

A क्रिया with an अधिकरणम् role can be called with dot syntax. The object to the left of the dot is bound as अधिकरणम्. Inside the body, अत्र is an alias for अधिकरणम्:

क्रिया निक्षेपः
  अपेक्षते
    अधिकरणम्: खातम्
    कर्म: संख्या
  अत्र.शेषः = अत्र.शेषः + कर्म
  फलम् अत्र
समाप्त

.निक्षेपः(कर्म: ५००)
# equivalent to:
निक्षेपः(अधिकरणम्: , कर्म: ५००)

Errors as values

See examples/10-doshah.karaka and examples/06-hastantaranam.karaka.

Creating a दोषः

दोषः "message" returns immediately from the enclosing function with a wrapped error value. It is not an exception; execution continues in the caller:

क्रिया भाजनम्
  अपेक्षते
    कर्म: संख्या
    करणम्: संख्या
  यदि करणम् ==  तदा
    दोषः "शून्येन भागः"
  समाप्त
  फलम् कर्म / करणम्
समाप्त

Checking for a दोषः

Use the अस्ति type predicate:

 = भाजनम्(कर्म: १०, करणम्: )
यदि  दोषः अस्ति तदा
  लिख(.विवरणम्)   # extract the message string
समाप्त

Recovery pattern

 = भाजनम्(कर्म: , करणम्: )
चल परिणामः = 
यदि  दोषः अस्ति तदा
  परिणामः = -
अन्यथा
  परिणामः = 
समाप्त

Uncaught दोषः

A दोषः statement at the top level — or one that propagates through all callers without being caught — triggers K2016 and exits with code 1. The REPL renders it as a value and stays alive.

Pipelines short-circuit

Once a दोषः is in a pipeline, all remaining stages are skipped. See the next section.

Rules and evidence

See examples/07-niyama.karaka.

नियमः defines a policy rule that returns one of three values:

निर्णयःअर्थ
अनुमतम्allowed
निषिद्धम्denied
अनिश्चितम्undecided
नियमः प्रवेश-अनुमतिः
  अपेक्षते
    कर्ता: उपयोगकर्ता
  यदि कर्ता.सक्रियः == "नहि" तदा
    प्रमाणम् "खाता-निष्क्रियः"
    फलम् निषिद्धम्
  समाप्त
  यदि कर्ता.भूमिका == "व्यवस्थापकः" तदा
    प्रमाणम् "व्यवस्थापक-भूमिका"
    फलम् अनुमतम्
  अन्यथा
    प्रमाणम् "अपर्याप्त-अधिकारः"
    फलम् निषिद्धम्
  समाप्त
समाप्त

प्रमाणम् "string" accumulates evidence on the active rule frame. Every branch should record a reason. प्रमाणम् outside a नियमः body is K2011.

Read accumulated evidence with .प्रमाणानि:

अनुज्ञा = प्रवेश-अनुमतिः(कर्ता: व१)
लिख(अनुज्ञा)             # → अनुमतम्
लिख(अनुज्ञा.प्रमाणानि)   # → [व्यवस्थापक-भूमिका]

When a दोषः escapes from inside a नियमः, it propagates to the caller as a दोषः value.

Pipelines

See examples/08-tatah.karaka.

ततः chains steps left to right. Each stage receives the previous result as its कर्म:

# text pipeline
परिणामः = "सूर्य-चन्द्र-तारा" ततः विभजनम्(करणम्: "-") ततः संयोजनम्(करणम्: " | ")
लिख(परिणामः)   # → सूर्य | चन्द्र | तारा

# numeric pipeline
संख्या =  ततः द्विगुणम् ततः योजय-दश

A stage may be a plain name (the value becomes the single argument) or a partial call with all roles except कर्म supplied.

If any stage returns a दोषः, all later stages are skipped. The दोषः is the pipeline result:

क्रिया विघ्नः
  अपेक्षते
    कर्म: संख्या
  दोषः "रुकावट"
समाप्त

 =  ततः द्विगुणम् ततः विघ्नः ततः साक्षी
यदि  दोषः अस्ति तदा
  लिख(.विवरणम्)   # साक्षी never ran
समाप्त

Standard library stages

फलम्कर्मकरणम्विवरण
विभजनम्वाक्यम्वाक्यम् separatorsplit string
संयोजनम्सूचीवाक्यम् separatorjoin list
योजनम्anyappend to list (अधिकरणम् = the list)
पठनम्वाक्यम् pathread file to string (CLI only)
लेखनम्वाक्यम् contentwrite string to file (CLI only; अधिकरणम् = path)

The गणितम् namespace holds: वर्गमूलम्, घातः, निरपेक्षम्, न्यूनतमम्, अधिकतमम्. See examples/09-ganitam.karaka.

The trace

Run with --अभिलेख to print an audit log to stderr:

karaka run examples/07-niyama.karaka --अभिलेख

Each traced invocation appears as:

प्रवेश-अनुमतिःकर्ता: व्यवस्थापकः⟩ → अनुमतम् [व्यवस्थापक-भूमिका]
लिखकर्म: अनुमतम्⟩ → फलम् शून्यम्

Format: नाम ⟨roles⟩ → result. For rules: result [evidence...]. For actions: फलम् result.

Only role-declared क्रिया and all नियमः invocations are traced. Positional-only क्रिया and जाति construction are not.

Add --jsonl for machine-readable output, one JSON object per line:

karaka run examples/07-niyama.karaka --अभिलेख --jsonl
{"seq":0,"depth":0,"kind":"niyama","name":"प्रवेश-अनुमतिः","roles":{"कर्ता":"व्यवस्थापकः"},"phala":"अनुमतम्","pramanani":["व्यवस्थापक-भूमिका"]}

JSONL keys: seq (invocation order), depth (call depth, 0-based), kind (kriya/niyama/builtin), name, roles (object), phala or dosha (one or the other), pramanani (array, niyama records only).

Style guide

Names

Use समास compound names for multi-concept identifiers: धन-हस्तान्तरणम्, प्रवेश-अनुमतिः. A hyphen is legal inside an identifier when the character after it is a Devanagari letter. Do not use underscores — they are not part of the character set.

Prefer Devanagari digits in Devanagari code: write १..१०, not 1..10.

Name rules after the permission they grant, not what they check: प्रवेश-अनुमतिः rather than भूमिका-जाँचः.

क्रिया size

Keep क्रिया small. If a body needs more than a few conditions, split it. Each role-declared क्रिया should do one thing that its roles describe completely.

Authority in roles, not comments

Roles state authority at every call site. Do not write a comment explaining direction when अपादानम् and सम्प्रदानम् already express it. Reserve comments for the non-obvious.

Reserved words as identifiers

Several Sanskrit words are reserved keywords in Karaka and cannot be used as identifiers. Two common traps:

The full list of keywords is: यदि, तदा, अन्यथा, यावत्, प्रत्येकम्, मध्ये, , वा, , फलम्, दोषः, प्रमाणम्, ततः, क्रिया, नियमः, जातिः, गुणः, चल, नित्य, अपेक्षते, अत्र, प्रारम्भः, समाप्त, सत्य, मिथ्या, शून्यम्, अनुमतम्, निषिद्धम्, अनिश्चितम्.

Definitions at top level

Definitions (क्रिया, नियमः, जातिः) must appear at the top level, not inside प्रारम्भः. The definition keyword and its name must be on the same line.

# correct
क्रिया योगः(, )
  फलम्  + 
समाप्त

# wrong — nested inside प्रारम्भः
प्रारम्भः
  क्रिया योगः(, )   # parse error
    फलम्  + 
  समाप्त
समाप्त

When to use roles vs positional

Use positional arguments for pure math or transformations where every argument has the same semantic weight:

क्रिया वर्गः()
  फलम्  * 
समाप्त

Use roles when any of these apply:

In almost every non-trivial business function, use roles. The extra labels pay for themselves the first time someone reads the call site six months later.