How Karaka behaves

Observed semantics reference for v0.1 "प्रथमा". Each fact here is grounded in the source or in a test case from the probe campaign. Where a behavior may surprise you, it is called out.

Source model

Encoding. Source is read as UTF-8. A leading BOM (U+FEFF) is stripped before processing. CRLF and bare CR are normalized to LF. The entire source is then NFC-normalized. All spans are byte offsets into the normalized text.

Columns. Diagnostic column numbers count grapheme clusters, not bytes or code points. क्रिया occupies 2 grapheme clusters (and reports as column width 2 when at the start of a line).

Identifiers. An identifier begins with a Devanagari base letter (includes independent vowels, consonants, avagraha, ॐ). It continues with Devanagari base letters, dependent signs (matras, virama, anusvara, visarga, nukta, etc.), and Devanagari digits. A hyphen followed immediately by a Devanagari letter extends the identifier: धन-हस्तान्तरणम् is one name. ASCII digits, underscores, and all non-Devanagari letters are not valid identifier characters. Mixing Devanagari and non-Devanagari letters in one identifier is K0002. An ASCII digit immediately after a Devanagari run (without a hyphen) is K0009. ॐ (U+0950) is a valid single-character identifier.

Juxtaposition. Two statements may share a line without a separator — juxtaposition is legal syntax. However, two definitions or a definition and a statement at the top level on the same line produce a targeted K1001 error (the parser catches this and reports "unexpected समाप्त" at the position where the second item begins). Use a newline or semicolon to separate them clearly.

Comments. A # starts a line comment; it consumes to end of line without emitting a newline token. A #= ... =# is a block comment. Block comments do not nest: the first =# closes the comment. A single-line block comment (#= text =# entirely on one line) is fully absorbed and does not act as a statement separator. A multi-line block comment acts as a newline, so it separates statements.

Definition keyword and name. The keyword (क्रिया, नियमः, जातिः) and the definition's name must appear on the same line. A newline between them is K1001.

Evaluation

Order. Evaluation is strict left-to-right. Subexpressions of a call are evaluated left to right before the call executes.

Short-circuit operators. (logical and) evaluates its right operand only when the left is सत्य. वा (logical or) evaluates its right operand only when the left is मिथ्या. Both require सत्यता (Bool) operands; a non-Bool on either side is K2002.

Arguments. All arguments to a call evaluate once, left to right, in the call expression, before any binding occurs.

Numeric types

Representations. Integers are i64. Decimals are f64.

Overflow. Integer arithmetic uses checked operations; overflow is K2018. The value i64::MIN (-9223372036854775808) cannot appear as a literal — the parser sees the minus as negation applied to i64::MAX + 1, which overflows.

Division. / always produces a decimal (दशांशः), regardless of whether the operands are integers. 5 / 4 is 1.25. Division by zero (integer 0 or decimal 0.0) is K2012.

Modulo. % requires integer operands. A non-integer operand is K2002. Division by zero in modulo is K2012.

Infinity. Decimal overflow through गणितम्.घातः(२.०, ११००.०) or equivalent produces अनन्तम् (positive infinity). Karaka renders positive infinity as अनन्तम् and negative infinity as -अनन्तम्. अनन्तम् - अनन्तम् produces असंख्यम् (NaN). Infinity is reachable; NaN is reachable through infinity arithmetic.

Ordering with अनन्तम् and असंख्यम्. अनन्तम् == अनन्तम् is सत्य (IEEE 754 reflexivity holds for infinity). All order comparisons (<, >, <=, >=) with असंख्यम् return मिथ्या. असंख्यम् == असंख्यम् is मिथ्या (NaN is not equal to itself).

Scope

Global scope. The top level is the global scope. Definitions and bindings created at the top level are visible inside क्रिया bodies and as role default expressions.

Blocks. Each block (यदि, यावत्, प्रत्येकम्, function bodies) creates a child scope. Names declared inside are not visible outside.

No closures. Functions see only the global scope, not the enclosing scope of their definition. v0.1 has no closures. A क्रिया defined at top level can access other top-level bindings, but a क्रिया defined inside another क्रिया would not see the outer क्रिया's locals (such nested definitions are not supported in v0.1; the parser only accepts definitions at the top level).

Shadowing. A चल or नित्य declaration inside a block shadows an outer name for the duration of that block.

Implicit declare. A plain assignment (क = व्यंजन) in a block where has no enclosing binding creates a new स्थिर (immutable) binding in the current scope. Assignment to a name that exists in an outer scope modifies that binding (subject to its mutability).

Equality and identity

Type== semantics
पूर्णसंख्या (Int)value equality
दशांशः (Dec)value equality; अनन्तम् == अनन्तम् is सत्य; असंख्यम् == असंख्यम् is मिथ्या
Int vs Decpromotes Int to f64; 3 == 3.0 is सत्य
वाक्यम् (Str)value equality
सत्यता (Bool)value equality
शून्यम्always equal to itself
निर्णयःcompares decisions only; evidence is ignored
सूची, कोशः, instancesidentity (same object); two structurally equal but distinct lists are not ==
दोषःidentity

Container interior mutability

The चल keyword on a binding controls whether that binding can be rebound to a different value. Container interiors — list elements and map entries — are always mutable; index assignment works regardless of whether the binding is चल. For जातिः instances, a गुण declared with चल inside the जातिः definition can be reassigned; one without चल is immutable after construction (K2006 on attempt).

Ranges

Ranges are inclusive at both ends: १..३ contains 1, 2, 3.

A reversed range (३..१) is empty. It never matches मध्ये and प्रत्येकम् over it executes zero iterations.

मध्ये (membership) on a range accepts only Int operands. A non-integer left operand is K2002.

Calls

Binding algorithm summary. When a role-declared क्रिया is called, unlabeled arguments fill positional parameters first, then one unlabeled argument fills the कर्म role if the क्रिया has exactly one role labeled कर्म and no positional parameters. Labeled arguments bind to their named roles. A role with a default expression is optional at the call site; the default evaluates against the global scope. A missing required role is K2004. An unrecognized role label is K2005. A duplicate role label at a call site is K2005.

Depth limit. The call depth limit is 512, shared across user क्रिया, नियमः, and builtin calls. A लिख call wrapped inside a user क्रिया consumes one depth level for the user क्रिया and one for लिख. Exceeding 512 is K2013.

Default evaluation. Default expressions evaluate against the global scope at each call, not at definition time.

नियम and प्रमाण

Frames. Each नियमः invocation pushes an evidence frame onto the interpreter's stack. प्रमाणम् "string" appends to the innermost नियम frame. A क्रिया body pushes a blocking frame: प्रमाणम् inside a क्रिया is K2011, even when that क्रिया is called from inside a नियमः.

Inner-rule isolation. When a नियमः calls another नियमः, each has its own evidence frame. The inner rule's evidence stays with its result; it does not merge into the outer rule's frame.

दोष propagation from rules. A दोषः that escapes from inside a नियमः body propagates out of the rule as a दोषः value (not as a निर्णयः). The caller receives the दोषः.

Trace (अभिलेख)

Eligibility. A क्रिया is traced only when it has at least one role declaration (अपेक्षते with one or more roles). Positional-only क्रिया (no अपेक्षते) are not traced. All नियमः invocations are traced. The लिख builtin is always traced. Other builtins (विभजनम्, संयोजनम्, योजनम्, पठनम्, लेखनम्) are traced. गणितम् namespace functions are not traced.

Pipeline stages. Every role-declared builtin (लिख, विभजनम्, संयोजनम्, योजनम्, पठनम्, लेखनम्) works as both a bare-Name stage and a Call-stage in a ततः pipeline; the piped value binds to कर्म. Builtins that require अधिकरणम् (योजनम्, लेखनम्) must supply it as a labeled arg in a Call-stage — a bare Name-stage with no labeled args will error K2004. गणितम् entries are not pipeline stages (K2003). पठनम्/लेखनम् without the os feature error K2003.

Entry snapshot. Role values are rendered and capped at 160 characters at call entry. Later mutations to containers do not change the snapshot.

Invocation order. Records appear in invocation (not return) order. Each record has a seq field counting from 0.

Depth. The depth field is 0 for top-level calls. Nested calls increment depth. The baseline is 0.

दोषः in trace. When a traced call returns a दोषः, the record carries a dosha field (not phala). Records are written even on the error path.

Pretty format. नाम ⟨role: value, ...⟩ → result. For नियमः: result [evidence1, evidence2, ...]. For क्रिया and builtins: फलम् result. Depth is shown by two-space indentation per level.

JSONL key order. seq, depth, kind, name, roles (object), then phala or dosha (one or the other, absent if not set), then pramanani (array, present only for niyama records).

JSONL header. When --jsonl is active, the first stderr line is {"kind":"header","unix":<secs>} instead of the Devanagari text header emitted in pretty mode. Every stderr line in jsonl mode is a JSON object.

REPL semantics

The REPL keeps a persistent session. Definitions and top-level bindings survive across inputs. Each input is parsed and executed as a complete fragment. A प्रारम्भः block in an input executes and its locals are discarded; globals mutated inside it persist.

शून्यम् values are suppressed: if the last expression in an input evaluates to शून्यम्, nothing is echoed. Non-शून्यम् expression values are echoed after any लिख output.

A दोषः returned from a called function is rendered as a value and does not terminate the session.

Multiple प्रारम्भः blocks in a single input are K2015. Each new input may have its own प्रारम्भः.

Semicolons separate statements on one line:

कारक> चल  = ;  =  + ; 
1

Limits and performance envelope

These numbers are from release builds on a modern laptop. They are indicative, not guarantees.

OperationMeasured
karaka run startup~10 ms
Fibonacci(25) recursive~0.36 s
1 million iteration loop~0.21 s
Parse 1 MB sourcelinear in input size
Map lookuplinear scan (insertion-ordered Vec)
Call depth limit512 (K2013 above)

Map lookup is O(n) in the number of keys. For large maps, use a list or restructure into smaller maps.

Diagnostics catalog

All diagnostic codes, with a one-line description and a minimal trigger.

Lexical (K0xxx)

CodeDescriptionTrigger
K0001Illegal character! or any non-Devanagari, non-symbol character
K0002Mixed-script identifierkarmaResult (Devanagari + Latin in one identifier)
K0003Invisible characterZero-width space (U+200B) in source
K0004Stray combining markA virama or matra without a preceding base letter
K0005Unterminated string"वाक्यम् with no closing "
K0006Bad escape sequence"\q" — unknown escape
K0007Unterminated block comment#= started but never closed
K0008Mixed digits in one literal१23 — Devanagari and ASCII digits in the same number
K0009ASCII digit continuing identifierकार्य3 — ASCII digit immediately after Devanagari
K0010Numeric literal out of rangeInteger exceeding i64::MAX
K0011Empty fraction३. with no digits after the decimal point

Parse (K1xxx)

CodeDescriptionTrigger
K1001Unexpected tokenसमाप्त with no matching opening keyword
K1002Expected expressionOperator without an operand
K1003Bad interpolation"{क ख}" — more than one expression inside {...}
K1004Unclosed blockप्रारम्भः with no समाप्त before end of file

Runtime (K2xxx)

CodeDescriptionTrigger
K2001Undefined nameUsing a name that has not been declared
K2002Type mismatchयदि ५ तदा — condition is not Bool; or % with a Dec operand
K2003Not callableCalling an integer or string as a function
K2004Missing required argumentOmitting a role with no default
K2005Bad role labelUsing a label not declared in अपेक्षते
K2006Immutable rebindingAssigning to a non-चल binding or a non-चल गुण
K2007Index out of boundsस[१०] when the list has fewer than 11 elements
K2008Arity errorलिख(१, २) — too many arguments
K2009No such member.foo on a type that has no field named foo
K2010नियमः result not निर्णयःA नियमः body returns a non-निर्णयः value (e.g. an Int)
K2011प्रमाणम् outside नियमःप्रमाणम् "x" at top level or inside a क्रिया body
K2012Division by zero१ / ० or ५ % ०
K2013Call depth exceededRecursion or call chain deeper than 512 levels
K2014Invalid assignment targetAssigning to a literal or an expression that is not a name, index, or member
K2015Multiple प्रारम्भःTwo प्रारम्भः blocks in the same program or REPL input
K2016Uncaught दोषःA दोषः that propagates to the top level without being caught
K2017Duplicate definitionDefining a क्रिया, नियमः, or जातिः with a name already in scope
K2018Numeric overflowInteger arithmetic that exceeds i64 range
K2019फलम्/दोषः inside यदि-expressionUsing फलम् or दोषः inside a यदि used in expression position