#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 Dec | promotes Int to f64; 3 == 3.0 is सत्य |
| वाक्यम् (Str) | value equality |
| सत्यता (Bool) | value equality |
| शून्यम् | always equal to itself |
| निर्णयः | compares decisions only; evidence is ignored |
| सूची, कोशः, instances | identity (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.
| Operation | Measured |
|---|---|
karaka run startup | ~10 ms |
| Fibonacci(25) recursive | ~0.36 s |
| 1 million iteration loop | ~0.21 s |
| Parse 1 MB source | linear in input size |
| Map lookup | linear scan (insertion-ordered Vec) |
| Call depth limit | 512 (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)
| Code | Description | Trigger |
|---|---|---|
| K0001 | Illegal character | ! or any non-Devanagari, non-symbol character |
| K0002 | Mixed-script identifier | karmaResult (Devanagari + Latin in one identifier) |
| K0003 | Invisible character | Zero-width space (U+200B) in source |
| K0004 | Stray combining mark | A virama or matra without a preceding base letter |
| K0005 | Unterminated string | "वाक्यम् with no closing " |
| K0006 | Bad escape sequence | "\q" — unknown escape |
| K0007 | Unterminated block comment | #= started but never closed |
| K0008 | Mixed digits in one literal | १23 — Devanagari and ASCII digits in the same number |
| K0009 | ASCII digit continuing identifier | कार्य3 — ASCII digit immediately after Devanagari |
| K0010 | Numeric literal out of range | Integer exceeding i64::MAX |
| K0011 | Empty fraction | ३. with no digits after the decimal point |
#Parse (K1xxx)
| Code | Description | Trigger |
|---|---|---|
| K1001 | Unexpected token | समाप्त with no matching opening keyword |
| K1002 | Expected expression | Operator without an operand |
| K1003 | Bad interpolation | "{क ख}" — more than one expression inside {...} |
| K1004 | Unclosed block | प्रारम्भः with no समाप्त before end of file |
#Runtime (K2xxx)
| Code | Description | Trigger |
|---|---|---|
| K2001 | Undefined name | Using a name that has not been declared |
| K2002 | Type mismatch | यदि ५ तदा — condition is not Bool; or % with a Dec operand |
| K2003 | Not callable | Calling an integer or string as a function |
| K2004 | Missing required argument | Omitting a role with no default |
| K2005 | Bad role label | Using a label not declared in अपेक्षते |
| K2006 | Immutable rebinding | Assigning to a non-चल binding or a non-चल गुण |
| K2007 | Index out of bounds | स[१०] when the list has fewer than 11 elements |
| K2008 | Arity error | लिख(१, २) — too many arguments |
| K2009 | No 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 |
| K2012 | Division by zero | १ / ० or ५ % ० |
| K2013 | Call depth exceeded | Recursion or call chain deeper than 512 levels |
| K2014 | Invalid assignment target | Assigning to a literal or an expression that is not a name, index, or member |
| K2015 | Multiple प्रारम्भः | Two प्रारम्भः blocks in the same program or REPL input |
| K2016 | Uncaught दोषः | A दोषः that propagates to the top level without being caught |
| K2017 | Duplicate definition | Defining a क्रिया, नियमः, or जातिः with a name already in scope |
| K2018 | Numeric overflow | Integer arithmetic that exceeds i64 range |
| K2019 | फलम्/दोषः inside यदि-expression | Using फलम् or दोषः inside a यदि used in expression position |