devabhasha 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +974 -0
- package/package.json +47 -0
- package/src/analyzer.js +125 -0
- package/src/bundler.js +129 -0
- package/src/cli.js +99 -0
- package/src/codegen.js +864 -0
- package/src/devserver.js +148 -0
- package/src/errors.js +71 -0
- package/src/index.js +30 -0
- package/src/io-browser.js +31 -0
- package/src/io-node.js +102 -0
- package/src/karaka-web.js +49 -0
- package/src/keywords.js +64 -0
- package/src/lexer.js +140 -0
- package/src/parser.js +687 -0
- package/src/server-node.js +120 -0
- package/src/server.js +182 -0
- package/src/stdlib.js +137 -0
- package/src/style.js +103 -0
- package/src/symbols.js +194 -0
- package/src/vibhakti.js +87 -0
package/README.md
ADDED
|
@@ -0,0 +1,974 @@
|
|
|
1
|
+
# देवभाषा · Devabhāṣā
|
|
2
|
+
|
|
3
|
+
A small **Sanskrit programming language that transpiles to JavaScript**, with a
|
|
4
|
+
web/DOM layer and a zero-build browser playground. Source is written in
|
|
5
|
+
Devanagari; output is readable JavaScript.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
कार्य अभिवादनम् (नाम) { function abhivaadanam(naama) {
|
|
9
|
+
फलम् "नमस्ते, " + नाम। → return ("namaste, " + naama);
|
|
10
|
+
} }
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
node src/cli.js run examples/namaste.deva # compile + execute (Node)
|
|
17
|
+
node src/cli.js build examples/ganaka.deva # → examples/ganaka.js
|
|
18
|
+
node build-playground.js # → playground/index.html
|
|
19
|
+
node test/test.js # run the test suite
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Open `playground/index.html` in any browser — no server needed.
|
|
23
|
+
|
|
24
|
+
## Language reference
|
|
25
|
+
|
|
26
|
+
| Sanskrit | Meaning | Compiles to |
|
|
27
|
+
|----------|---------|-------------|
|
|
28
|
+
| `चर` / `नियत` | varies / fixed | `let` / `const` |
|
|
29
|
+
| `कार्य` | work to be done | `function` (statement **or** expression) |
|
|
30
|
+
| `फलम्` | fruit / result | `return` |
|
|
31
|
+
| `यदि` / `अन्यथा` | if / otherwise | `if` / `else` |
|
|
32
|
+
| `यावत्` | as long as | `while` |
|
|
33
|
+
| `प्रत्येकम् (x : समूह)` | for each | `for (const x of …)` |
|
|
34
|
+
| `भङ्ग` / `अनुवृत्तम्` | break / continue | `break` / `continue` |
|
|
35
|
+
| `सत्यम्` / `असत्यम्` / `शून्यम्` | true / false / void | `true` / `false` / `null` |
|
|
36
|
+
| `दर्शय(…)` | cause to show | `console.log(…)` |
|
|
37
|
+
| `अङ्गम्(tag, …)` | limb / part | `document.createElement` + children |
|
|
38
|
+
| `योजय(node[, target])` | join / attach | append to DOM |
|
|
39
|
+
| `श्रोता(node, ev, fn)` | listener | `addEventListener` |
|
|
40
|
+
|
|
41
|
+
- Statements end with the **danda** `।` or `;` (both optional before `}`/EOF).
|
|
42
|
+
- Comments begin with `#`.
|
|
43
|
+
- Numbers may use Devanagari digits (`०१२…`) or ASCII.
|
|
44
|
+
- Identifiers may be Devanagari; they're transliterated to stable ASCII in the
|
|
45
|
+
output (`गणना` → `gannanaa`), so the JS is portable and debuggable.
|
|
46
|
+
|
|
47
|
+
## कारक — Pāṇinian case-role construction
|
|
48
|
+
|
|
49
|
+
The signature feature. Sanskrit marks a word's grammatical role with its
|
|
50
|
+
**vibhakti (case ending)**, not its position. Devabhāṣā uses this for
|
|
51
|
+
**order-free DOM construction** via the verb `रचय` (racaya, "construct").
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
रचय पटः वाक्यम् "वर्धय" स्पर्शाय करणेन वर्धक।
|
|
55
|
+
│ │ │ │
|
|
56
|
+
│ │ │ └ करण/instrumental → handler
|
|
57
|
+
│ │ └ सम्प्रदान/dative → event ("click")
|
|
58
|
+
│ └ कर्म/accusative → content
|
|
59
|
+
└ कर्तृ/nominative → tag (<button>)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Each argument's **role comes from its ending**, so the arguments may be
|
|
63
|
+
written in **any order** and compile to identical JavaScript:
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
__DB.construct({ tag: "button", content: "वर्धय", event: "click", handler: वर्धक })
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The seven recognized cases and their kāraka roles:
|
|
70
|
+
|
|
71
|
+
| Case (vibhakti) | Kāraka | DOM slot |
|
|
72
|
+
|-----------------|--------|----------|
|
|
73
|
+
| प्रथमा nominative `-ः` | कर्तृ agent | element tag |
|
|
74
|
+
| द्वितीया accusative `-म्` | कर्म patient | content |
|
|
75
|
+
| तृतीया instrumental `-ेन` | करण instrument | handler |
|
|
76
|
+
| चतुर्थी dative `-ाय` | सम्प्रदान recipient | event |
|
|
77
|
+
| पञ्चमी ablative `-ात्` | अपादान source | data source |
|
|
78
|
+
| षष्ठी genitive `-स्य` | सम्बन्ध relation | attribute |
|
|
79
|
+
| सप्तमी locative `-े` | अधिकरण locus | mount parent |
|
|
80
|
+
|
|
81
|
+
**Scope (honest):** the case engine (`src/vibhakti.js`) implements the
|
|
82
|
+
a-stem (अकारान्त) singular paradigm — the most common declension, covering
|
|
83
|
+
most coined technical vocabulary. Other stem-classes and genders extend it
|
|
84
|
+
by adding rows to the paradigm table. ASCII words and uninflected stems are
|
|
85
|
+
correctly *not* treated as case-marked, which is what keeps free word order
|
|
86
|
+
parseable.
|
|
87
|
+
|
|
88
|
+
Tag and event vocabulary lives in `src/karaka-web.js` (`पट`→button,
|
|
89
|
+
`शीर्ष`→h1, `स्पर्श`→click, …).
|
|
90
|
+
|
|
91
|
+
## समास — compound composition (nested DOM trees)
|
|
92
|
+
|
|
93
|
+
Where kāraka handles *one* element's roles, **समास (samāsa, "compound")**
|
|
94
|
+
handles how elements **nest** — the structural counterpart. A block form
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
रचय <tag-nominative> { ...children... }
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
is a **तत्पुरुष** (container) whose body is a **द्वन्द्व** (sibling list).
|
|
101
|
+
It composes recursively, so DOM trees of any depth are just nested `रचय`
|
|
102
|
+
blocks. Container tags use the nominative (`मूलः`=div, `सूचीः`=ul); content
|
|
103
|
+
uses the accusative marker `वाक्यम्`:
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
चर पृष्ठम् = रचय मूलः {
|
|
107
|
+
रचय शीर्षः वाक्यम् "देवभाषा"।
|
|
108
|
+
रचय वाक्यः वाक्यम् "संस्कृतेन रचितम्।"।
|
|
109
|
+
रचय सूचीः {
|
|
110
|
+
रचय पङ्क्तिः वाक्यम् "प्रथमम्"।
|
|
111
|
+
रचय पङ्क्तिः वाक्यम् "द्वितीयम्"।
|
|
112
|
+
}
|
|
113
|
+
}।
|
|
114
|
+
योजय(पृष्ठम्)।
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
compiles to nested `__DB.construct({ tag, children: [...] })` and renders:
|
|
118
|
+
|
|
119
|
+
```html
|
|
120
|
+
<div><h1>देवभाषा</h1><p>संस्कृतेन रचितम्।</p>
|
|
121
|
+
<ul><li>प्रथमम्</li><li>द्वितीयम्</li></ul></div>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
kāraka and समās compose: a child element can carry its own case-marked
|
|
125
|
+
slots (an event handler, attributes) while sitting in the tree. Children
|
|
126
|
+
attach by DOM `append`, which *moves* nodes, so the model stays correct
|
|
127
|
+
whether or not a child was auto-mounted.
|
|
128
|
+
|
|
129
|
+
## रूप — CSS-in-Sanskrit styling
|
|
130
|
+
|
|
131
|
+
`रूप` (rūpa, "form/appearance") attaches styles to an element, with **both
|
|
132
|
+
property names and value words in Sanskrit**:
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
रचय पटः वाक्यम् "स्पृश" रूप {
|
|
136
|
+
वर्णः: श्वेतः, # color: white
|
|
137
|
+
पृष्ठभूमिः: रक्तः, # backgroundColor: crimson
|
|
138
|
+
अन्तरालः: "10px 20px", # padding (quoted CSS passes through)
|
|
139
|
+
कोणवृत्तिः: "6px", # borderRadius
|
|
140
|
+
सीमा: अदृश्यम् # border: none
|
|
141
|
+
}।
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The vocabulary (`src/style.js`) covers color (`वर्णः`), background
|
|
145
|
+
(`पृष्ठभूमिः`), typography (`अक्षरमानम्`/font-size, `अक्षरभारः`/font-weight,
|
|
146
|
+
`संरेखणम्`/text-align), the box model (`अन्तरालः`/padding,
|
|
147
|
+
`बाह्यान्तरः`/margin, `कोणवृत्तिः`/border-radius), and fl/layout
|
|
148
|
+
(`प्रदर्शनम्`/display, `दिक्`/flex-direction, `न्यायः`/justify). Value words
|
|
149
|
+
include colors (`रक्तः`→crimson, `नीलः`→navy, `केसरः`→#F4C430) and keywords
|
|
150
|
+
(`केन्द्रम्`→center, `प्रवाहः`→flex, `गुरुः`→bold).
|
|
151
|
+
|
|
152
|
+
Resolution rules: a **known Sanskrit value word** becomes a CSS literal; a
|
|
153
|
+
**bare variable** stays a reference (`वर्णः: मुख्यवर्णः` reads the variable);
|
|
154
|
+
a **quoted string** (`"18px"`) and an **unknown CSS property** (`cursor`)
|
|
155
|
+
pass through unchanged; an **expression** (`अक्षरमानम्: n + "px"`) is
|
|
156
|
+
emitted as-is. रूप composes with समās — whole styled trees are nested `रचय`
|
|
157
|
+
blocks each carrying their own `रूप`.
|
|
158
|
+
|
|
159
|
+
## सूची-दत्तांशः — data rendering & named styles
|
|
160
|
+
|
|
161
|
+
**List rendering.** A समās child can be any expression that yields DOM
|
|
162
|
+
nodes, and array children are flattened — so mapping a data array with
|
|
163
|
+
`.प्रतिचित्रय` (map) renders a list:
|
|
164
|
+
|
|
165
|
+
```
|
|
166
|
+
ग्रन्थाः.प्रतिचित्रय(कार्य(ग) {
|
|
167
|
+
फलम् रचय पङ्क्तिः वाक्यम् ग.नाम। # one <li> per data item
|
|
168
|
+
})
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
placed inside a `रचय सूचीः { … }` produces a `<ul>` of `<li>`s, one per row.
|
|
172
|
+
|
|
173
|
+
**Named styles (रूपनाम).** Declare a reusable style once and apply it by
|
|
174
|
+
name, with optional per-element overrides:
|
|
175
|
+
|
|
176
|
+
```
|
|
177
|
+
रूपनाम कार्डः = रूप { पृष्ठभूमिः: श्वेतः, अन्तरालः: "12px", कोणवृत्तिः: "8px" }।
|
|
178
|
+
|
|
179
|
+
रचय मूलः रूप कार्डः { … } # apply the named style
|
|
180
|
+
रचय मूलः रूप कार्डः { पृष्ठभूमिः: नीलः } { … } # named base + override
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
`रूपनाम X = रूप {…}` compiles to `const X = {…CSS…}`; a reference compiles
|
|
184
|
+
to `Object.assign({}, X)`, and a base-plus-override to
|
|
185
|
+
`Object.assign({}, X, {…})` so overrides win. A subtle parser point: after a
|
|
186
|
+
named reference, `{` is disambiguated by lookahead — `{ word: … }` is a
|
|
187
|
+
style override, anything else is a समās children block.
|
|
188
|
+
|
|
189
|
+
Together these make real data-driven UIs (`examples/suchi.deva`): a styled
|
|
190
|
+
container whose children are a heading plus a mapped list of styled cards.
|
|
191
|
+
|
|
192
|
+
## भाव — reactive state
|
|
193
|
+
|
|
194
|
+
`भाव` (bhāva, "state/condition") declares a reactive state cell, and
|
|
195
|
+
`दृश्य` (dṛśya, "view") declares a region that **re-renders automatically**
|
|
196
|
+
when any state it reads changes. The model is subscribe-on-read /
|
|
197
|
+
re-run-on-write: a view subscribes to every `भाव` it reads while rendering,
|
|
198
|
+
and a later write re-runs just that view.
|
|
199
|
+
|
|
200
|
+
```
|
|
201
|
+
भाव गणकः = ०। # a reactive counter cell
|
|
202
|
+
|
|
203
|
+
दृश्य { # a self-updating view
|
|
204
|
+
रचय मूलः {
|
|
205
|
+
रचय शीर्षः वाक्यम् "गणना: " + गणकः। # reads गणकः → subscribes
|
|
206
|
+
रचय पटः वाक्यम् "वर्धय"
|
|
207
|
+
स्पर्शाय करणेन कार्य(){ गणकः++। }। # writes गणकः → re-renders
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
The syntax is transparent: a `भाव` name reads as its value and assigns
|
|
213
|
+
normally — under the hood `गणकः` compiles to `गणकः()` (read), `गणकः = v`
|
|
214
|
+
to `गणकः(v)` (write), and `गणकः++` / `गणकः += n` desugar through the cell.
|
|
215
|
+
`दृश्य { … }` mounts to the page root by default, or `दृश्य (container) { … }`
|
|
216
|
+
to a chosen element. A view's final expression is what gets rendered.
|
|
217
|
+
|
|
218
|
+
Because a view is just a समās tree, reactivity composes with everything:
|
|
219
|
+
styled, data-mapped, event-wired trees that rebuild on state change.
|
|
220
|
+
`examples/todo.deva` is a complete reactive todo app — `भाव` task list,
|
|
221
|
+
`.प्रतिचित्रय` rendering each task, buttons that add/remove and re-render.
|
|
222
|
+
|
|
223
|
+
### प्रभाव — fine-grained reactivity
|
|
224
|
+
|
|
225
|
+
`दृश्य` is *coarse*: when any cell it read changes, it re-runs and rebuilds
|
|
226
|
+
its whole subtree. That's simple and fine for small views, but it's not how
|
|
227
|
+
modern frontend frameworks (Solid, Svelte) achieve speed — they update only
|
|
228
|
+
the exact DOM bound to the changed value. Devabhāṣā now has that model too, as
|
|
229
|
+
a layer alongside `दृश्य`:
|
|
230
|
+
|
|
231
|
+
- `प्रभाव(कार्य(){ … })` (prabhāva, "influence/effect") runs the function once,
|
|
232
|
+
**tracks exactly which `भाव` cells it reads**, and re-runs *only that
|
|
233
|
+
function* when one of those cells changes — nothing else.
|
|
234
|
+
- `बन्ध(कार्य(){ … })` (bandha, "binding") returns a text node whose content
|
|
235
|
+
is kept in sync by an effect; when its dependencies change, **only that one
|
|
236
|
+
node's text updates** — no rebuild, no lost focus or scroll.
|
|
237
|
+
|
|
238
|
+
```
|
|
239
|
+
भाव गणकः = ०।
|
|
240
|
+
चर नोड = बन्ध(कार्य(){ फलम् पाठ"गणना: {गणकः}"। })। # a live text node
|
|
241
|
+
गणकः = गणकः + १। # ONLY नोड's text updates — the surrounding DOM is untouched
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
The engine is a real signal/effect system: a subscriber stack tracks the
|
|
245
|
+
current effect, each cell records its subscribers, and an effect cleans up its
|
|
246
|
+
old dependencies before every re-run — so conditional reads (`यदि (ध्वजः) {
|
|
247
|
+
… ब … }`) correctly drop a dependency when the branch stops taking it (the
|
|
248
|
+
classic stale-subscription bug, handled). `दृश्य` and `प्रभाव` coexist: the
|
|
249
|
+
coarse view is still there for simple cases, fine-grained for performance.
|
|
250
|
+
|
|
251
|
+
**Fine-grained is also automatic.** A `रचय` whose content is *dynamic* (reads
|
|
252
|
+
a `भाव` cell) and is used **outside** a `दृश्य` compiles straight to a bound
|
|
253
|
+
text node — no `बन्ध` call needed:
|
|
254
|
+
|
|
255
|
+
```
|
|
256
|
+
भाव गणकः = ०।
|
|
257
|
+
चर शीर्षम् = रचय शीर्षः वाक्यम् पाठ"गणना: {गणकः}"। # dynamic content
|
|
258
|
+
गणकः = गणकः + १। # ONLY this node's text updates, in place
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
The compiler detects the `भाव` read and emits a fine-grained binding; static
|
|
262
|
+
content (no `भाव` read) stays a plain value with zero overhead, and content
|
|
263
|
+
*inside* a `दृश्य` stays coarse so the two models never double-update. So you
|
|
264
|
+
get fine-grained performance by default, just by writing dynamic content.
|
|
265
|
+
|
|
266
|
+
### घटकाः — components & props
|
|
267
|
+
|
|
268
|
+
A **component** is just a `कार्य` that takes props and returns a `रचय` tree —
|
|
269
|
+
no new syntax, because functions and `रचय` already compose. You call it like
|
|
270
|
+
any function and reuse it freely:
|
|
271
|
+
|
|
272
|
+
```
|
|
273
|
+
कार्य पत्रम् (शीर्षकम्, मूल्यम्) {
|
|
274
|
+
फलम् रचय मूलः { रचय शीर्षः वाक्यम् शीर्षकम्। रचय वाक्यः वाक्यम् मूल्यम्। }।
|
|
275
|
+
}
|
|
276
|
+
चर अ = पत्रम्("नाम", "रामः")। # reuse with different props
|
|
277
|
+
चर ब = पत्रम्("वयः", "तिंशत्")।
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
The one thing that needs care is **reactive props** — a prop that should stay
|
|
281
|
+
*live* across the call boundary. Passing a `भाव` cell normally reads its value
|
|
282
|
+
at the call site (a dead snapshot). Instead, wrap it in `सूत्र(…)` (sūtra,
|
|
283
|
+
"thread"), which passes a *reactive reference* — the component renders it and
|
|
284
|
+
it updates fine-grained when the cell changes, while only *that* node updates:
|
|
285
|
+
|
|
286
|
+
```
|
|
287
|
+
भाव गणकः = ०।
|
|
288
|
+
चर कार्डः = आँकडापत्रम्("क्लिक्", सूत्र(गणकः), "navy")। # live reactive prop
|
|
289
|
+
गणकः = गणकः + १। # the card's value updates in place; siblings untouched
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
`सूत्र` is **explicit on purpose** — reactivity crossing a boundary is visible
|
|
293
|
+
at the call site rather than implicit, matching the language's preference for
|
|
294
|
+
predictable over clever. Static props (plain values) and callback props
|
|
295
|
+
(passing a `कार्य` for, e.g., a click handler) work as ordinary arguments.
|
|
296
|
+
`examples/घटकाः.deva` composes a reusable stat-card component (used three
|
|
297
|
+
times) with reactive, static, and callback props.
|
|
298
|
+
|
|
299
|
+
### आवली — keyed list rendering
|
|
300
|
+
|
|
301
|
+
Rendering a list with `.प्रतिचित्रय` inside a `दृश्य` rebuilds *every* row when
|
|
302
|
+
the data changes — losing each row's DOM state (focus, scroll, input values).
|
|
303
|
+
`आवली` (āvalī, "row/series") is **keyed reconciliation**: it identifies rows by
|
|
304
|
+
a stable key and, on each change, **reuses** the DOM nodes of surviving keys
|
|
305
|
+
(moving them if reordered), **builds** only genuinely new keys, and **removes**
|
|
306
|
+
vanished ones — the model Solid and Svelte use.
|
|
307
|
+
|
|
308
|
+
```
|
|
309
|
+
भाव वस्तूनि = [कोष{ कुं: १, … }, कोष{ कुं: २, … }, …]।
|
|
310
|
+
चर पटः = आवली(
|
|
311
|
+
सूत्र(वस्तूनि), # the reactive data
|
|
312
|
+
कार्य(व){ फलम् व.कुं। }, # key — a stable identity per item
|
|
313
|
+
कार्य(व){ फलम् रचय वाक्यः वाक्यम् व.नाम। } # render one row
|
|
314
|
+
)।
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
Because nodes are reused by key, **a row's DOM state moves with it** across a
|
|
318
|
+
reorder — the defining benefit of keyed rendering. The reconciler runs inside
|
|
319
|
+
an effect, so it re-runs only when the data signal changes, and it touches the
|
|
320
|
+
DOM minimally (no rebuild of unchanged rows). `examples/आवली.deva` is a
|
|
321
|
+
shuffling colored list where each row keeps its identity through reordering.
|
|
322
|
+
|
|
323
|
+
### Reactive styles & cleanup
|
|
324
|
+
|
|
325
|
+
Fine-grained binding extends to **style properties**: a `रूप { }` value that
|
|
326
|
+
reads a `भाव` cell (outside a `दृश्य`) updates *just that one CSS property* in
|
|
327
|
+
place when the cell changes — no rebuild, the rest of the element untouched.
|
|
328
|
+
|
|
329
|
+
```
|
|
330
|
+
भाव वर्णः = "tomato"।
|
|
331
|
+
चर पटः = रचय मूलः रूप { पृष्ठभूमिः: वर्णः, अन्तरालः: "40px" }। # bg is reactive
|
|
332
|
+
वर्णः = "steelblue"। # ONLY background-color updates
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
Each dynamic property gets its own effect; static properties (and styles inside
|
|
336
|
+
a `दृश्य`) stay plain with zero overhead. The same applies to a `सक्रियः ?
|
|
337
|
+
रक्तः : धूसरः` ternary — the property re-evaluates fine-grained.
|
|
338
|
+
|
|
339
|
+
For teardown, `सफाई(कार्य(){ … })` (saphāī, "cleanup") registers a hook inside
|
|
340
|
+
a `प्रभाव` that runs **before the effect's next re-run** — the place to clear a
|
|
341
|
+
timer, remove a listener, or cancel a subscription so reactive code doesn't
|
|
342
|
+
leak:
|
|
343
|
+
|
|
344
|
+
```
|
|
345
|
+
प्रभाव(कार्य(){
|
|
346
|
+
चर घटी = कालचक्र(कार्य(){ … }, १०००)।
|
|
347
|
+
सफाई(कार्य(){ कालनाशः(घटी)। })। # tear down the old timer before re-running
|
|
348
|
+
})।
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
Refs need no special form — `रचय` already returns the DOM node (`चर न = रचय
|
|
352
|
+
…`), so you hold the handle directly and can read or mutate it.
|
|
353
|
+
|
|
354
|
+
### आलस्यचित्रम् — lazy-loaded images
|
|
355
|
+
|
|
356
|
+
`आलस्यचित्रम्(src, opts)` (ālasya-citra, "lazy image") builds an `img` that
|
|
357
|
+
loads its real source only when it scrolls into view, via `IntersectionObserver`
|
|
358
|
+
— the standard technique for fast image-heavy pages. It shows `opts.placeholder`
|
|
359
|
+
(or nothing) until then, keeps the real URL in `data-src`, sets the native
|
|
360
|
+
`loading="lazy"` hint, and falls back to eager loading where the observer is
|
|
361
|
+
unavailable:
|
|
362
|
+
|
|
363
|
+
```
|
|
364
|
+
चर चित्रम् = आलस्यचित्रम्(लेख.चित्रम्, कोष{
|
|
365
|
+
alt: लेख.शीर्षकम्,
|
|
366
|
+
placeholder: "data:image/svg+xml,…" # a tiny inline placeholder
|
|
367
|
+
})।
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
`examples/ब्लॉग.deva` is a complete **blog website**: a post list whose cards are
|
|
371
|
+
components with lazy-loaded cover images, reactive view-switching between the
|
|
372
|
+
list and a full-post view (a `प्रभाव` effect rebuilding the content area when
|
|
373
|
+
the selected post changes), and a back button — composing components, effects,
|
|
374
|
+
and lazy images into a real app.
|
|
375
|
+
|
|
376
|
+
`examples/vyaya.deva` goes further — a reactive **expense tracker** with a
|
|
377
|
+
live computed total, category filter buttons (whose active colour comes from
|
|
378
|
+
a `सक्रियः ? रक्तः : धूसरः` ternary, color words resolved in-expression),
|
|
379
|
+
`.गालय` filtering, and add-entry interaction: several `भाव` cells and derived
|
|
380
|
+
views composing into one app.
|
|
381
|
+
|
|
382
|
+
`examples/flappy.deva` (विहगः) is a real-time **Flappy-Bird-style game** — a
|
|
383
|
+
different stress test entirely: an animation loop, continuous state mutation,
|
|
384
|
+
collision detection, and keyboard input. It uses three general-purpose host
|
|
385
|
+
bindings added for interactive programs: `कालचक्र(fn, ms)` (kālacakra, "wheel
|
|
386
|
+
of time" → a repeating timer / game loop), `कालनाशः(id)` (stop a timer), and
|
|
387
|
+
`कुञ्जिश्रोता(fn)` (kuñjiśrotā, "key-listener" → a keyboard handler receiving
|
|
388
|
+
the pressed key). The whole game state lives in one `भाव` cell that the loop
|
|
389
|
+
mutates ~30×/sec; absolute positioning uses the `शीर्षात्`/`वामतः`/`अधस्तात्`/
|
|
390
|
+
`दक्षिणतः` (top/left/bottom/right) and `स्तरः` (z-index) style properties.
|
|
391
|
+
|
|
392
|
+
Building it surfaced a real fix to the reactive model: a `भाव` cell skipped
|
|
393
|
+
re-rendering when written the same *reference* — fine for immutable updates,
|
|
394
|
+
but a game mutates its state object in place, so the view never refreshed. The
|
|
395
|
+
guard now skips only unchanged **primitives**; object/array state always
|
|
396
|
+
re-renders, which is the behavior mutable game loops (and most reactive code)
|
|
397
|
+
expect.
|
|
398
|
+
|
|
399
|
+
## गणित — the math module
|
|
400
|
+
|
|
401
|
+
A comprehensive mathematics layer. Elementary and transcendental functions
|
|
402
|
+
come from a native bridge to JavaScript's `Math` (exact, full precision);
|
|
403
|
+
the statistics and number-theory layer (`examples/ganita.deva`) is written
|
|
404
|
+
*in Devabhāṣā* on top of them — the self-hosting payoff applied to a
|
|
405
|
+
standard library.
|
|
406
|
+
|
|
407
|
+
**Native bridge** — `गणित.<fn>(…)`:
|
|
408
|
+
`वर्गमूलम्` (sqrt), `घनमूलम्` (cbrt), `घातः` (pow), `निरपेक्षम्` (abs),
|
|
409
|
+
`अधःपातः`/`ऊर्ध्वपातः` (floor/ceil), `सन्निकर्षः` (round), `धनर्णचिह्नम्` (sign),
|
|
410
|
+
`ज्या`/`कोटिज्या`/`स्पर्शज्या` (sin/cos/tan), `विलोमज्या` etc. (asin…),
|
|
411
|
+
`घातीयम्` (exp), `लघुगणकः` (log), `अधिकतमः`/`न्यूनतमः` (max/min),
|
|
412
|
+
`यादृच्छिकम्` (random). Constants: `गणित.पाई` (π), `गणित.यूलरांकः` (e),
|
|
413
|
+
`गणित.मूलद्वि` (√2).
|
|
414
|
+
|
|
415
|
+
A nice piece of history: **ज्या (jyā) and कोटिज्या (koṭijyā) are the actual
|
|
416
|
+
classical Sanskrit names for sine and cosine** — the words that, via Arabic
|
|
417
|
+
*jiba → jayb*, became the Latin *sinus* and our "sine."
|
|
418
|
+
|
|
419
|
+
**Statistics & number theory** (written in Devabhāṣā, `examples/ganita.deva`):
|
|
420
|
+
`माध्यम्` (mean), `मध्यमा` (median), `बहुलकः` (mode), `शतमक` (percentile),
|
|
421
|
+
`प्रसरणम्`/`मानविचलनम्` (variance/stddev), `न्यूनतमम्`/`अधिकतमम्`/`परिसरः`
|
|
422
|
+
(min/max/range), `योगफलम्`/`गुणनफलम्` (sum/product), `क्रमणम्` (sort),
|
|
423
|
+
`महत्तमसमापवर्तकः`/`लघुत्तमसमापवर्त्यः` (gcd/lcm), `क्रमगुणितम्` (factorial),
|
|
424
|
+
`अभाज्यः` (is-prime), plus angle conversion (`अंशेभ्यःरेडियनम्`), `सीमन`
|
|
425
|
+
(clamp), `रैखिकान्तर्वेशनम्` (lerp), and `परिवृत्तिः` (round to N places).
|
|
426
|
+
Verified against Python's `statistics`/`numpy` to full precision.
|
|
427
|
+
|
|
428
|
+
**Transcendentals from first principles.** To show the language isn't merely
|
|
429
|
+
forwarding to `Math`, `घातीयश्रेणी` (exp) and `ज्याश्रेणी` (sin) are computed
|
|
430
|
+
in pure Devabhāṣā via their Taylor series — and agree with the native bridge
|
|
431
|
+
to ~15 digits.
|
|
432
|
+
|
|
433
|
+
```
|
|
434
|
+
चर दत्तांशः = [४, ८, १५, १६, २३, ४२]।
|
|
435
|
+
दर्शय("माध्यम्:", माध्यम्(दत्तांशः))। # 18
|
|
436
|
+
दर्शय("मानविचलनम्:", मानविचलनम्(दत्तांशः))। # 12.3153…
|
|
437
|
+
दर्शय("e via Taylor:", घातीयश्रेणी(१))। # 2.71828… (no Math used)
|
|
438
|
+
दर्शय("ज्या(π/६):", गणित.ज्या(गणित.पाई / ६))। # 0.5
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
## अन्तर्न्यासः — string interpolation
|
|
442
|
+
|
|
443
|
+
`पाठ"…"` (pāṭha, "text") marks an interpolated string: any `{expr}` inside is
|
|
444
|
+
evaluated and spliced in, compiling to a JS template literal.
|
|
445
|
+
|
|
446
|
+
```
|
|
447
|
+
दर्शय(पाठ"{नाम} वर्षाणि {वयः}")। # variables
|
|
448
|
+
रचय वाक्यः वाक्यम् पाठ"योगः: {योगम्(व्ययाः)} रूप्यकाणि"। # any expression
|
|
449
|
+
दर्शय(पाठ"\{ब्रेस\} रक्षितम्")। # \{ \} for literal braces
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
It is **opt-in via the पाठ marker** for a deliberate reason: making every
|
|
453
|
+
`"…"` interpolate would mis-read the literal braces in existing strings —
|
|
454
|
+
including the JS that the self-hosted compiler emits as string literals — so
|
|
455
|
+
plain `"…"` stays completely literal, and only `पाठ"…"` interpolates. The
|
|
456
|
+
marker must touch the quote, so `पाठ` remains usable as an ordinary
|
|
457
|
+
identifier. Interpolation holds full expressions (calls, member access,
|
|
458
|
+
arithmetic, even nested strings), and is the idiomatic way to build labels
|
|
459
|
+
and views — `examples/vyaya.deva` uses it throughout.
|
|
460
|
+
|
|
461
|
+
## मधुरचिह्नानि — operator sugar
|
|
462
|
+
|
|
463
|
+
Convenience operators that desugar to the core language:
|
|
464
|
+
|
|
465
|
+
- **compound assignment** — `x += y`, `-=`, `*=`, `/=`, `%=` (works on
|
|
466
|
+
variables, object fields, and array elements); `x += y` becomes `x = x + y`
|
|
467
|
+
- **ternary** — `परीक्षा ? तदा : अन्यथा`, chainable for `else-if` ladders
|
|
468
|
+
- **null-coalescing** — `a ?? b` (yields `b` only when `a` is null/undefined)
|
|
469
|
+
- **increment / decrement** — `i++`, `i--`
|
|
470
|
+
|
|
471
|
+
```
|
|
472
|
+
चर कुल = ०।
|
|
473
|
+
प्रत्येकम् (x : [१, २, ३, ४]) {
|
|
474
|
+
कुल += x > २ ? x : ०। # add x only when x > 2
|
|
475
|
+
}
|
|
476
|
+
दर्शय(कुल)। # 7
|
|
477
|
+
|
|
478
|
+
चर नाम = दत्त ?? "अज्ञातः"। # default when दत्त is null
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
These live in the JavaScript-hosted compiler; the self-hosted bootstrap
|
|
482
|
+
sources deliberately don't use them, so the fixpoint is unaffected.
|
|
483
|
+
|
|
484
|
+
## आयात / निर्यात — the module system
|
|
485
|
+
|
|
486
|
+
Modules use **compile-time resolution and linking** (the Rust/Python
|
|
487
|
+
lineage, not C-style textual inclusion): each `.deva` file is compiled
|
|
488
|
+
independently, exports are explicit, and a bundler resolves the import graph
|
|
489
|
+
and links everything into one self-contained program.
|
|
490
|
+
|
|
491
|
+
**Exporting** — prefix any declaration with `निर्यात` (niryāta, "sending
|
|
492
|
+
out"). Anything not marked is private to its module:
|
|
493
|
+
|
|
494
|
+
```
|
|
495
|
+
# गणितागारम्.deva
|
|
496
|
+
निर्यात नियत पाई = ३।
|
|
497
|
+
निर्यात कार्य द्वि (न) { फलम् न * २। }
|
|
498
|
+
कार्य गुप्तम् () { … } # private — not importable
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
**Importing** — `आयात` (āyāta, "incoming") with `आ` (ā, "from") as the
|
|
502
|
+
source preposition, in three forms:
|
|
503
|
+
|
|
504
|
+
```
|
|
505
|
+
आयात { द्वि, पाई } आ "गणितागारम्"। # named — bind specific exports
|
|
506
|
+
आयात * रूपेण ग आ "गणितागारम्"। # namespace — ग.द्वि, ग.पाई
|
|
507
|
+
आयात "उपस्करः"। # side-effect — just run the module
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
`devabhasha build entry.deva` (or `run`) resolves every `आयात` relative to
|
|
511
|
+
the importing file (appending `.deva`), compiles each module once, orders
|
|
512
|
+
them dependency-first (topological sort), and links them: each module
|
|
513
|
+
becomes an IIFE returning its export object, named imports destructure it,
|
|
514
|
+
namespace imports bind the whole object. Diamond dependencies compile the
|
|
515
|
+
shared module a single time; missing modules and exports of non-declarations
|
|
516
|
+
are compile-time errors. See `examples/modules/` for a math library split
|
|
517
|
+
into a module and consumed by a main file.
|
|
518
|
+
|
|
519
|
+
The bundler lives in `src/bundler.js`; `compileModule(src)` returns
|
|
520
|
+
`{ code, exports, imports }` for tooling.
|
|
521
|
+
|
|
522
|
+
## आदर्शकोशः — the standard library (written in Devabhāṣā)
|
|
523
|
+
|
|
524
|
+
The standard library is itself written **in Devabhāṣā**, as `.deva` modules
|
|
525
|
+
under `examples/stdlib/` — the clearest proof the module system earns its
|
|
526
|
+
keep, and the "move features to libraries" principle in practice. Each is
|
|
527
|
+
plain Devabhāṣā built on the array/string/object primitives, with zero
|
|
528
|
+
compiler support:
|
|
529
|
+
|
|
530
|
+
- **सूची** (list): `योगः` (sum), `गुणनफलम्` (product), `न्यूनीकरणम्` (fold),
|
|
531
|
+
`अन्वेषय` (find), `सन्ति`/`सर्वे` (any/all), `न्यूनतमम्`/`महत्तमम्`
|
|
532
|
+
(min/max), `अद्वितीयम्` (unique), `आदिमानि`/`शेषाणि` (take/drop),
|
|
533
|
+
`समतलीकृ` (flatten), `परिसरः` (range).
|
|
534
|
+
- **कोष** (object): `कुञ्जयः`/`मूल्यानि`/`प्रविष्टयः` (keys/values/entries),
|
|
535
|
+
`अस्ति` (has-key), `सङ्ख्या` (count), `विलयः` (merge),
|
|
536
|
+
`प्रतिचित्रयमूल्यानि` (map-values), `गालयकुञ्जीभिः` (pick). Built on the
|
|
537
|
+
`सङ्ग्रह` (Object) global.
|
|
538
|
+
- **पाठ** (string): `आवर्तय` (repeat), `वामपूरणम्` (pad-left),
|
|
539
|
+
`प्रथमाक्षरोच्च` (capitalize), `पदानि` (words), `व्युत्क्रमः` (reverse),
|
|
540
|
+
`परिवर्तय_सर्वम्` (replace-all), `रिक्तः` (is-blank).
|
|
541
|
+
- **परीक्षा** (test framework): `परीक्षा(नाम, fn)` registers and runs a test,
|
|
542
|
+
`अपेक्ष(actual)` returns an asserter (`.समम्`/equal, `.असमम्`/not-equal,
|
|
543
|
+
`.सत्यम्ता`/truthy, `.असत्यम्ता`/falsy), `समम्(अ, ब)` is a standalone deep
|
|
544
|
+
structural equality, and `सारः()` prints the `N पास, M फेल` tally. Staying
|
|
545
|
+
true to the no-exceptions design, assertions **record** their outcome into a
|
|
546
|
+
collector rather than throwing.
|
|
547
|
+
|
|
548
|
+
Use them like any module — and they compose, importing from several at once:
|
|
549
|
+
|
|
550
|
+
```
|
|
551
|
+
आयात { परिसरः, योगः } आ "सूची"।
|
|
552
|
+
आयात { आवर्तय } आ "पाठ"।
|
|
553
|
+
दर्शय(योगः(परिसरः(१, ५)))। # 10
|
|
554
|
+
दर्शय(आवर्तय("=", योगः([१,२])))। # "==="
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
With `परीक्षा`, the language tests itself: Devabhāṣā programs (including the
|
|
558
|
+
standard library above) can be tested *in Devabhāṣā*, completing the
|
|
559
|
+
self-hosting story that already covers the compiler. `examples/परीक्षा-उदाहरणम्.deva`
|
|
560
|
+
tests the `सूची` module using `परीक्षा` — both written in the language:
|
|
561
|
+
|
|
562
|
+
```
|
|
563
|
+
आयात { परीक्षा, अपेक्ष, सारः } आ "stdlib/परीक्षा"।
|
|
564
|
+
आयात { योगः, अद्वितीयम् } आ "stdlib/सूची"।
|
|
565
|
+
परीक्षा("अद्वितीयम्", कार्य(){ अपेक्ष(अद्वितीयम्([१,२,२,३])).समम्([१,२,३])। })।
|
|
566
|
+
सारः()। # → "1 पास, 0 फेल"
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
Reflection helpers underpin it: `स्वरूपम्(v)` gives a value's type as a
|
|
570
|
+
Sanskrit name (`अङ्क`, `वाक्`, `सूची`, `कोष`, `सत्यासत्य`, `रिक्त`), and
|
|
571
|
+
`सूचीवत्(v)` tests for a list — enabling the framework's deep equality.
|
|
572
|
+
|
|
573
|
+
## उपकरणानि — tooling (VS Code extension, dev server, source maps & LSP)
|
|
574
|
+
|
|
575
|
+
**VS Code extension.** `devabhasha-1.0.0.vsix` is an installable VS Code
|
|
576
|
+
extension (Extensions panel → "Install from VSIX…", or
|
|
577
|
+
`code --install-extension devabhasha-1.0.0.vsix`). It gives `.deva` files
|
|
578
|
+
syntax highlighting (a TextMate grammar), completion, hover, go-to-definition,
|
|
579
|
+
rename, and live diagnostics, plus a "Run current file" command. Rather than
|
|
580
|
+
spawning the stdio language server, the extension wires the **same
|
|
581
|
+
dependency-free analyzer core** (`src/analyzer.js`) directly to VS Code's
|
|
582
|
+
provider APIs in-process — so the server and the extension are two frontends
|
|
583
|
+
over one analyzer, with no bundled language-client and no child process. The
|
|
584
|
+
analyzer graph is bundled to CommonJS for the extension with `node
|
|
585
|
+
build-extension.js` (esbuild is a build-time tool only; nothing extra ships in
|
|
586
|
+
the `.vsix`).
|
|
587
|
+
|
|
588
|
+
**Dev server with live reload.** `devabhasha serve file.deva [--port N]` starts
|
|
589
|
+
a zero-dependency dev server (Node's built-in `http` + `fs.watch`, live reload
|
|
590
|
+
over Server-Sent Events — no packages) that:
|
|
591
|
+
|
|
592
|
+
- serves an HTML page running your compiled web program, mounted at `#मूलम्`,
|
|
593
|
+
- watches the source (and its `आयात` imports) and **recompiles + reloads the
|
|
594
|
+
browser automatically** when you save, and
|
|
595
|
+
- shows compile *and* runtime errors on the page instead of a blank screen,
|
|
596
|
+
staying up so you can fix and re-save.
|
|
597
|
+
|
|
598
|
+
So the loop is: edit `.deva`, save, see it update in the browser — the modern
|
|
599
|
+
front-end dev experience, for a Sanskrit language.
|
|
600
|
+
|
|
601
|
+
**Source maps.** `devabhasha build file.deva --sourcemap` emits the JS plus a
|
|
602
|
+
standard **Source Map v3** (`.js.map`, with `sourcesContent` and a
|
|
603
|
+
`//# sourceMappingURL` comment), so browser dev-tools and Node stack traces
|
|
604
|
+
point back at the original Devanagari source. Mapping is at statement
|
|
605
|
+
granularity — each statement node carries its source line/col, and the
|
|
606
|
+
codegen tracks output position as it emits. The maps are VLQ-encoded and
|
|
607
|
+
validated against the standard `source-map` library. The API is
|
|
608
|
+
`compileWithMap(source) → { code, map }`.
|
|
609
|
+
|
|
610
|
+
**Language server (LSP).** `src/server.js` is an editor-agnostic Language
|
|
611
|
+
Server speaking the Language Server Protocol over stdio — any LSP client
|
|
612
|
+
(VS Code, Neovim, Helix…) can connect by running `node src/server.js`. It
|
|
613
|
+
provides:
|
|
614
|
+
|
|
615
|
+
- **diagnostics** — compile errors with precise ranges, live as you type
|
|
616
|
+
(cleared automatically when the code becomes valid);
|
|
617
|
+
- **completion** — the full vocabulary (keywords, stdlib methods, गणित
|
|
618
|
+
constants, रूप properties and color words, element tags, events), filtered
|
|
619
|
+
by the prefix being typed;
|
|
620
|
+
- **hover** — what a Sanskrit word means and what it translates to
|
|
621
|
+
(`वर्णः` → color, `रक्तः` → crimson, `वर्गमूलम्` → Math.sqrt);
|
|
622
|
+
- **go-to-definition** — jump from any use of a name to where it is bound;
|
|
623
|
+
- **rename** — rename a binding and every reference that resolves to it,
|
|
624
|
+
atomically, as one workspace edit.
|
|
625
|
+
|
|
626
|
+
Go-to-definition and rename are backed by a **scope-aware symbol table**
|
|
627
|
+
(`src/symbols.js`) that walks the AST tracking lexical scopes — `चर`/`नियत`,
|
|
628
|
+
`कार्य` names and parameters, `भाव`/`रूपनाम` declarations, and `प्रत्येकम्`
|
|
629
|
+
loop variables. It respects **shadowing**: a renamed inner parameter never
|
|
630
|
+
touches an outer variable of the same name, and vice versa. (This is why
|
|
631
|
+
identifiers and declaration names carry source positions — JS-side metadata,
|
|
632
|
+
stripped before the self-hosted parser comparison, so the bootstrap is
|
|
633
|
+
unaffected.)
|
|
634
|
+
|
|
635
|
+
The analysis is pure functions in `src/analyzer.js` (`diagnostics`,
|
|
636
|
+
`completions`, `hover`, `definition`, `renameOccurrences`, `wordAt`);
|
|
637
|
+
`server.js` is a thin JSON-RPC wrapper, so the same engine can power other
|
|
638
|
+
front-ends.
|
|
639
|
+
|
|
640
|
+
## सञ्चिका / जाल — file & network I/O
|
|
641
|
+
|
|
642
|
+
I/O is **layered**: a program calls the Sanskrit surface (`सञ्चिका` for
|
|
643
|
+
files, `जाल` for the network), which the codegen lowers to a runtime
|
|
644
|
+
interface `__IO`, which a host-injected **backend** implements. The program
|
|
645
|
+
is bound to the *interface*, never to Node — so the same code runs against
|
|
646
|
+
the Node backend (CLI), a browser backend, or an in-memory test backend
|
|
647
|
+
(this is exactly how the test suite proves it, running one program against
|
|
648
|
+
two backends).
|
|
649
|
+
|
|
650
|
+
Every operation is **async and returns a `परिणाम` (Result)** — composing the
|
|
651
|
+
two foundations above. You `प्रतीक्षा` (await) the call and inspect the
|
|
652
|
+
Result; a failure is a value, never a thrown exception, so a missing file
|
|
653
|
+
can't crash the program:
|
|
654
|
+
|
|
655
|
+
```
|
|
656
|
+
असमकालिक कार्य मुख्यम् () {
|
|
657
|
+
प्रतीक्षा सञ्चिका.लिख("संदेशः.txt", "नमस्ते")। # write
|
|
658
|
+
चर र = प्रतीक्षा सञ्चिका.पठ("संदेशः.txt")। # read
|
|
659
|
+
यदि (र.सफल) { दर्शय(र.मूल्यम्)। } अन्यथा { दर्शय(र.दोषः)। }
|
|
660
|
+
|
|
661
|
+
चर जालम् = प्रतीक्षा जाल.आनय("https://api.example.com")। # fetch
|
|
662
|
+
यदि (जालम्.सफल) { दर्शय(जालम्.मूल्यम्.स्थितिः)। } # → HTTP status
|
|
663
|
+
}
|
|
664
|
+
मुख्यम्()।
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
**File ops** (`सञ्चिका`): `पठ` (read), `लिख` (write), `विद्यते` (exists),
|
|
668
|
+
`निष्कासय` (remove), `सूचीकृ` (list a directory). **Network** (`जाल`):
|
|
669
|
+
`आनय` (fetch), whose Result value carries `स्थितिः` (status), `पाठः` (body
|
|
670
|
+
text), and `सफलम्` (ok). The Node backend lives in `src/io-node.js`;
|
|
671
|
+
`devabhasha run` injects it, and `devabhasha build` inlines it so the
|
|
672
|
+
produced `.js` is self-contained under Node. See `examples/io.deva`.
|
|
673
|
+
|
|
674
|
+
## सेवक — HTTP server (backend)
|
|
675
|
+
|
|
676
|
+
Because Devabhāṣā compiles to JavaScript and runs on Node, it can be a
|
|
677
|
+
*backend* language too. `सेवक(handler, port)` (sevaka, "server") starts an HTTP
|
|
678
|
+
server — the same host-bridge pattern as `सञ्चिका`/`जाल`, lowered to a `__SRV`
|
|
679
|
+
runtime the Node backend implements (`src/server-node.js`; `run` injects it,
|
|
680
|
+
`build` inlines it). The handler receives a **request** and **response**, both
|
|
681
|
+
keyed in Devanagari:
|
|
682
|
+
|
|
683
|
+
```
|
|
684
|
+
सेवक(असमकालिक कार्य(अनुरोधः, प्रत्युत्तरम्){
|
|
685
|
+
यदि (अनुरोधः.मार्गः == "/जनाः") { # route by path
|
|
686
|
+
यदि (अनुरोधः.रीतिः == "GET") { # branch by method
|
|
687
|
+
प्रत्युत्तरम्.प्रेषय_जेसन(सूची)। # JSON response
|
|
688
|
+
} अन्यथा {
|
|
689
|
+
चर परि = प्रतीक्षा अनुरोधः.देहम्_जेसन()। # parse JSON body → परिणाम
|
|
690
|
+
यदि (परि.सफल) {
|
|
691
|
+
सूची.योजय(परि.मूल्यम्)।
|
|
692
|
+
प्रत्युत्तरम्.स्थिति(२०१).प्रेषय_जेसन(परि.मूल्यम्)।
|
|
693
|
+
} अन्यथा {
|
|
694
|
+
प्रत्युत्तरम्.स्थिति(४००).प्रेषय_जेसन(कोष{ दोषः: "bad" })।
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
} अन्यथा {
|
|
698
|
+
प्रत्युत्तरम्.स्थिति(४०४).लेखय("न प्राप्तम्")।
|
|
699
|
+
}
|
|
700
|
+
}, ८०८०)।
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
The **request** (`अनुरोधः`) exposes `मार्गः` (decoded path), `रीतिः` (method),
|
|
704
|
+
`शीर्षाणि` (headers), `प्रश्नाः` (query params), and async body readers `देहम्()`
|
|
705
|
+
(text) and `देहम्_जेसन()` (→ `परिणाम`, so a malformed body is a value, never a
|
|
706
|
+
crash). The **response** (`प्रत्युत्तरम्`) is chainable: `स्थिति(code)`,
|
|
707
|
+
`शीर्षम्(k, v)`, `लेखय(text)`, `प्रेषय_जेसन(value)`. The handler may be
|
|
708
|
+
`असमकालिक` (async); if it throws, the server returns 500 and stays up.
|
|
709
|
+
|
|
710
|
+
This is a deliberately minimal HTTP primitive — enough to write a real JSON API
|
|
711
|
+
or static server (`examples/सेवकः.deva` is a small books API), not a
|
|
712
|
+
production framework like Express. Its value is the same as the rest of the
|
|
713
|
+
language: an honest, teachable surface — and, paired with the reactive frontend,
|
|
714
|
+
the rare ability to write *both ends of a web app in one Sanskrit source*. A
|
|
715
|
+
routing/middleware library written in Devabhāṣā on top of `सेवक` (the
|
|
716
|
+
"framework" proper, like `परीक्षा` is for testing) is the natural next layer.
|
|
717
|
+
|
|
718
|
+
`examples/पूर्णस्तर/` is a **full-stack app**: a reactive Sanskrit frontend
|
|
719
|
+
(`अग्रिम.deva`) fetching from a Sanskrit HTTP/JSON backend (`पश्चिम.deva`) over
|
|
720
|
+
real HTTP, sharing the language, the Result error model, and the JSON shapes.
|
|
721
|
+
Run it with `node चालय.mjs` and open `http://localhost:8100`.
|
|
722
|
+
|
|
723
|
+
### मार्गकः — a routing library (written in Devabhāṣā)
|
|
724
|
+
|
|
725
|
+
`सेवक` is the bare primitive; `मार्गकः` (mārgaka, "router") is the *framework*
|
|
726
|
+
layer on top — and, like `परीक्षा` the test framework, it's **written in
|
|
727
|
+
Devabhāṣā itself** (`examples/stdlib/मार्गकः.deva`), not baked into the runtime.
|
|
728
|
+
Import it and register routes by method and path:
|
|
729
|
+
|
|
730
|
+
```
|
|
731
|
+
आयात { मार्गकः } आ "मार्गकः"।
|
|
732
|
+
चर ऐप = मार्गकः()।
|
|
733
|
+
ऐप.उपयुज्(असमकालिक कार्य(अ, प्र, अग्रे){ दर्शय(अ.मार्गः)। प्रतीक्षा अग्रे()। })। # middleware
|
|
734
|
+
ऐप.प्राप्("/ग्रन्थाः", कार्य(अ, प्र){ प्र.प्रेषय_जेसन(सूची)। })। # GET
|
|
735
|
+
ऐप.प्राप्("/ग्रन्थाः/:अंकः", कार्य(अ, प्र){ ... अ.प्राचलाः["अंकः"] ... })। # path param
|
|
736
|
+
ऐप.स्थापय("/ग्रन्थाः", असमकालिक कार्य(अ, प्र){ ... })। # POST
|
|
737
|
+
ऐप.चालय(८०८०)।
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
The API: `प्राप्` (GET), `स्थापय` (POST), `परिवर्तय` (PUT), `निष्कासय` (DELETE),
|
|
741
|
+
`उपयुज्` (middleware), `चालय` (listen). Path parameters (`:अंकः`) are captured
|
|
742
|
+
into `अनुरोधः.प्राचलाः`. Middleware follows the Koa contract — `असमकालिक`, and
|
|
743
|
+
`प्रतीक्षा अग्रे()` to pass control downstream — which is what lets the server
|
|
744
|
+
await the whole chain before ending the response.
|
|
745
|
+
|
|
746
|
+
**On "optimum":** each route's path is **compiled once at registration** into a
|
|
747
|
+
segment list (static segments + `:param` markers), and routes are **grouped by
|
|
748
|
+
method**, so a request does only a segment-by-segment compare against the routes
|
|
749
|
+
of its own method — no regex, no re-parsing per request. A radix tree would win
|
|
750
|
+
at very large route counts; at app scale this segment-compare is effectively
|
|
751
|
+
optimal and far simpler, an honest tradeoff stated in the source.
|
|
752
|
+
`examples/stdlib/मार्गक-उदाहरणम्.deva` is a books API built on it.
|
|
753
|
+
|
|
754
|
+
**JSON & structured data.** `प्रदत्त` (pradatta, "data") parses and serializes
|
|
755
|
+
JSON, both returning a `परिणाम` (since `JSON.parse` throws and the language
|
|
756
|
+
has none): `प्रदत्त.विश्लेषय(पाठः)` (parse) and `प्रदत्त.सूत्रय(मूल्यम्)`
|
|
757
|
+
(serialize). `अङ्कय(पाठः)` parses a string to a number, also as a Result. The
|
|
758
|
+
I/O layer adds JSON convenience ops so a config or API response becomes usable
|
|
759
|
+
data in one step: `सञ्चिका.पठप्रदत्त`/`लिखप्रदत्त` (read/write JSON files) and
|
|
760
|
+
`जाल.आनयप्रदत्त` (fetch + parse, value carries `प्रदत्तम्`). These compose with
|
|
761
|
+
`प्रतीक्षा` and `अथवा`:
|
|
762
|
+
|
|
763
|
+
```
|
|
764
|
+
चर विन्यासः = (प्रतीक्षा सञ्चिका.पठप्रदत्त("config.json")) अथवा कोष{ }। # parsed config, or {}
|
|
765
|
+
चर सङ्ख्या = अङ्कय(आदानम्) अथवा ०। # parsed number, or 0
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
## असमकालिक — async & await
|
|
769
|
+
|
|
770
|
+
For operations that complete later (timers, and the upcoming I/O layer),
|
|
771
|
+
Devabhāṣā has real `async`/`await` — the model JS, Python, and Rust all
|
|
772
|
+
converged on, and a near-direct map to JS since the target is JS.
|
|
773
|
+
|
|
774
|
+
- `असमकालिक कार्य …` (asamakālika, "asynchronous") marks an async function;
|
|
775
|
+
it returns a promise (`आश्वासन`).
|
|
776
|
+
- `प्रतीक्षा <expr>` (pratīkṣā, "waiting") awaits a promise, valid only
|
|
777
|
+
inside an `असमकालिक` function — the compiler enforces this (a top-level or
|
|
778
|
+
nested-sync `प्रतीक्षा` is a compile error, matching JS's coloring rule).
|
|
779
|
+
- promises chain with `.ततः` (then), `.दोषे` (catch), `.अन्ततः` (finally).
|
|
780
|
+
|
|
781
|
+
```
|
|
782
|
+
असमकालिक कार्य मुख्यम् () {
|
|
783
|
+
चर परि = प्रतीक्षा आनय(कोषः)। # await a promise
|
|
784
|
+
यदि (परि.सफल) { दर्शय(परि.मूल्यम्)। } # …of a परिणाम (Result)
|
|
785
|
+
अन्यथा { दर्शय(परि.दोषः)। }
|
|
786
|
+
}
|
|
787
|
+
मुख्यम्()।
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
async composes with the Result model: a fallible async operation returns a
|
|
791
|
+
*promise of a* `परिणाम`, so `प्रतीक्षा` yields a Result the caller inspects —
|
|
792
|
+
honest async **and** honest errors, with no exceptions. Event handlers can be
|
|
793
|
+
async too (`स्पर्शाय करणेन असमकालिक कार्य(){ … प्रतीक्षा … }`), the usual
|
|
794
|
+
shape for I/O-on-click.
|
|
795
|
+
|
|
796
|
+
## परिणाम — the error model (Result values)
|
|
797
|
+
|
|
798
|
+
Devabhāṣā handles fallible operations with **explicit Result values**, not
|
|
799
|
+
exceptions — keeping with the language's preference for visible over hidden
|
|
800
|
+
control flow (and avoiding a new non-local jump in the self-hosted compiler).
|
|
801
|
+
A fallible function returns a `परिणाम` (pariṇāma, "result/outcome"), built
|
|
802
|
+
with one of two constructors:
|
|
803
|
+
|
|
804
|
+
- `साधितम्(मूल्यम्)` (sādhitam, "achieved") — success, carrying a value
|
|
805
|
+
- `विफलम्(दोषः)` (viphalam, "failed") — failure, carrying an error
|
|
806
|
+
|
|
807
|
+
A `परिणाम` has three fields: `सफल` (ok?), `मूल्यम्` (the value), `दोषः`
|
|
808
|
+
(the error). The caller inspects it with ordinary `यदि`:
|
|
809
|
+
|
|
810
|
+
```
|
|
811
|
+
कार्य भाग (अंशः, हरः) {
|
|
812
|
+
यदि (हरः == ०) { फलम् विफलम्("शून्येन भागः")। } # Err
|
|
813
|
+
फलम् साधितम्(अंशः / हरः)। # Ok
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
चर फल = भाग(१०, २)।
|
|
817
|
+
यदि (फल.सफल) { दर्शय("ok:", फल.मूल्यम्)। }
|
|
818
|
+
अन्यथा { दर्शय("दोष:", फल.दोषः)। }
|
|
819
|
+
```
|
|
820
|
+
|
|
821
|
+
Results compose: a function can map an `Ok` value and pass an `Err` through
|
|
822
|
+
unchanged, which is how fallible steps chain without exceptions. The
|
|
823
|
+
constructors live in a host-independent prelude (`__RT`), so Result works in
|
|
824
|
+
any environment — including the I/O layer, where operations return `परिणाम`
|
|
825
|
+
rather than throwing.
|
|
826
|
+
|
|
827
|
+
**`अथवा` — value-or-fallback.** The common "use the value, or a default if it
|
|
828
|
+
failed" pattern would otherwise be a four-line `यदि/अन्यथा` block. `अथवा`
|
|
829
|
+
(athavā, "or else") collapses it to an expression: it yields the Result's
|
|
830
|
+
`मूल्यम्` when `सफल`, else a fallback that is **evaluated lazily** (only on
|
|
831
|
+
failure). It's pure expression sugar — no hidden control flow — and composes
|
|
832
|
+
with `await`, which is exactly the I/O case:
|
|
833
|
+
|
|
834
|
+
```
|
|
835
|
+
चर सामग्री = (प्रतीक्षा सञ्चिका.पठ("config.txt")) अथवा "रिक्तम्"। # default on read failure
|
|
836
|
+
चर सङ्ख्या = संख्यांकय(आदानम्) अथवा ०। # default on parse failure
|
|
837
|
+
```
|
|
838
|
+
|
|
839
|
+
`अथवा` chains right-associatively (`अ अथवा ब अथवा ग` tries each in turn,
|
|
840
|
+
first `Ok` wins) and also guards plain `null`/non-Result values, so it
|
|
841
|
+
doubles as a nullish fallback.
|
|
842
|
+
|
|
843
|
+
## दोषनिरूपणम् — error reporting
|
|
844
|
+
|
|
845
|
+
Compiler errors are structured (`DevabhashaError` with `line`, `col`, and a
|
|
846
|
+
`kind` of lex/parse/codegen) and render with source context and a caret:
|
|
847
|
+
|
|
848
|
+
```
|
|
849
|
+
अक्षरदोषः (lex error): अज्ञातं चिह्नम् (unknown character) '@'
|
|
850
|
+
line 2, column 9
|
|
851
|
+
|
|
852
|
+
२ | दर्शय(a @ b)।
|
|
853
|
+
| ^
|
|
854
|
+
```
|
|
855
|
+
|
|
856
|
+
Line numbers display in Devanagari numerals. The same formatting appears in
|
|
857
|
+
the CLI (`devabhasha run`) and live in the playground as you type. Use
|
|
858
|
+
`formatError(err, source)` to render any caught `DevabhashaError`.
|
|
859
|
+
|
|
860
|
+
## मानकपुस्तकालय — standard library (strings, objects, arrays)
|
|
861
|
+
|
|
862
|
+
The machinery a compiler is made of. These are what make self-hosting
|
|
863
|
+
possible: a lexer needs to index strings, accumulate substrings, push
|
|
864
|
+
records onto arrays, and read object fields.
|
|
865
|
+
|
|
866
|
+
**Strings** — `.दीर्घता` (length), `.अक्षरः(i)` (charAt), `.सङ्केतः(i)`
|
|
867
|
+
(charCodeAt), `.खण्ड(a,b)` (slice), `.अस्ति(x)` (includes), `.विभज(s)`
|
|
868
|
+
(split), `.उच्च`/`.नीच` (upper/lower), `.आरभते`/`.समाप्यते` (starts/ends).
|
|
869
|
+
|
|
870
|
+
**Arrays** — `.योजय(x)` (push), `.अपनय()` (pop), `.दीर्घता` (length),
|
|
871
|
+
`.प्रतिचित्रय(fn)` (map), `.गालय(fn)` (filter), `.सम्मील(s)` (join),
|
|
872
|
+
`.अनुक्रमणिका(x)` (indexOf), indexing via `सूची[i]`.
|
|
873
|
+
|
|
874
|
+
**Builtins** — `संकेताक्षर(code)` (saṅketākṣara, "code-letter") →
|
|
875
|
+
`String.fromCharCode`, e.g. `संकेताक्षर(९२)` yields a backslash. This is
|
|
876
|
+
what lets the self-hosted codegen build escape characters without writing
|
|
877
|
+
escape literals.
|
|
878
|
+
|
|
879
|
+
**Objects** — the `कोष` (kośa, "dictionary") literal:
|
|
880
|
+
|
|
881
|
+
```
|
|
882
|
+
चर व्यक्तिः = कोष { नाम: "राम", आयुः: ३० }।
|
|
883
|
+
दर्शय(व्यक्तिः.नाम)। # field access
|
|
884
|
+
व्यक्तिः.आयुः = ३१। # field assignment
|
|
885
|
+
```
|
|
886
|
+
|
|
887
|
+
Property names live in their own namespace, so a word that's a keyword
|
|
888
|
+
elsewhere (`योजय` = DOM mount) is still usable as a method name
|
|
889
|
+
(`सूची.योजय` = array push). The stdlib name→JS mapping lives in
|
|
890
|
+
`src/stdlib.js`.
|
|
891
|
+
|
|
892
|
+
### Self-hosting milestone
|
|
893
|
+
|
|
894
|
+
With strings, objects, and arrays in place, compiler machinery can now be
|
|
895
|
+
written *in Devabhāṣā itself*.
|
|
896
|
+
|
|
897
|
+
**All three compiler stages are written in Devabhāṣā, and the bootstrap is
|
|
898
|
+
a closed fixpoint** — the compiler reproduces itself byte-for-byte.
|
|
899
|
+
|
|
900
|
+
- `examples/lexer.deva` — port of `src/lexer.js`. Character classes are
|
|
901
|
+
built from codepoint arithmetic (no regex); keyword/operator tables are
|
|
902
|
+
Devabhāṣā data.
|
|
903
|
+
- `examples/parser.deva` — port of `src/parser.js`: recursive descent +
|
|
904
|
+
Pratt expression parsing. Since Devabhāṣā functions don't close over
|
|
905
|
+
outer mutable state, the parser threads an explicit state object
|
|
906
|
+
`अ = कोष { शब्दाः, स्थानम् }` through every function.
|
|
907
|
+
- `examples/codegen.deva` — port of `src/codegen.js`: the AST→JS emitter,
|
|
908
|
+
the Devanagari→ASCII transliterator `आईडी`, and string quoting. The
|
|
909
|
+
quoter builds every special character (backslash, quote, newline, tab)
|
|
910
|
+
from its code point via the `संकेताक्षर` (String.fromCharCode) builtin,
|
|
911
|
+
so the codegen source contains **no escape literals** — which is what
|
|
912
|
+
lets it reproduce its own source exactly.
|
|
913
|
+
|
|
914
|
+
Verified by the standard compiler-bootstrap fixpoint check
|
|
915
|
+
(`test/fixpoint.test.js`):
|
|
916
|
+
|
|
917
|
+
1. the JS-hosted compiler compiles the three `.deva` sources → a
|
|
918
|
+
Devabhāṣā-hosted compiler (stage 1);
|
|
919
|
+
2. that compiler recompiles all three sources (stage 2);
|
|
920
|
+
3. a compiler built from stage-2 output recompiles them again (stage 3);
|
|
921
|
+
4. **stage 2 === stage 3, byte-for-byte, for all three components** — the
|
|
922
|
+
same criterion GCC/Rust/Go use to certify a self-hosting bootstrap.
|
|
923
|
+
|
|
924
|
+
The stage-3 compiler — built entirely from Devabhāṣā-generated JavaScript,
|
|
925
|
+
with no original toolchain involved — runs real programs (recursion,
|
|
926
|
+
objects, arrays) correctly. `node selfhost-demo.mjs` shows the full
|
|
927
|
+
self-hosted compiler compiling and running Fibonacci.
|
|
928
|
+
|
|
929
|
+
## Architecture
|
|
930
|
+
A textbook transpiler pipeline — each stage is one file, no dependencies:
|
|
931
|
+
|
|
932
|
+
```
|
|
933
|
+
source .deva
|
|
934
|
+
│ src/lexer.js tokenize Devanagari → token stream
|
|
935
|
+
▼
|
|
936
|
+
tokens
|
|
937
|
+
│ src/parser.js recursive descent (statements) + Pratt (expressions) → AST
|
|
938
|
+
▼
|
|
939
|
+
AST
|
|
940
|
+
│ src/codegen.js tree-walk → JavaScript text (+ optional DOM runtime)
|
|
941
|
+
▼
|
|
942
|
+
JavaScript
|
|
943
|
+
```
|
|
944
|
+
|
|
945
|
+
- **`src/keywords.js`** — the *only* Sanskrit-aware file. Edit vocabulary here
|
|
946
|
+
without touching the engine. The lexer maps each Devanagari keyword to a
|
|
947
|
+
language-neutral token type, so the parser and codegen never see Sanskrit.
|
|
948
|
+
- **`src/index.js`** — `compile(source, { includeRuntime }) → string`.
|
|
949
|
+
- **`src/cli.js`** — `build` / `run` commands.
|
|
950
|
+
- **`build-playground.js`** — inlines the ES modules into one HTML file.
|
|
951
|
+
|
|
952
|
+
## Extending it
|
|
953
|
+
|
|
954
|
+
- **Add a keyword:** one line in `keywords.js`. If it needs new syntax, add a
|
|
955
|
+
parse branch in `parser.js` and an emit case in `codegen.js`.
|
|
956
|
+
- **Add an operator:** add to `OPERATORS` (longest-first) and to `BINARY_PREC`.
|
|
957
|
+
- **Richer web layer:** the runtime in `codegen.js` (`__DB`) is intentionally
|
|
958
|
+
tiny — extend `el` to support a JSX-like compound (`समास`) syntax, or add a
|
|
959
|
+
reactivity primitive.
|
|
960
|
+
|
|
961
|
+
## Suggested roadmap from here
|
|
962
|
+
|
|
963
|
+
1. **Source maps** — you have line/col on every token already; thread them into
|
|
964
|
+
codegen for real debugging.
|
|
965
|
+
2. **A JSX-like element syntax** using compounds, e.g.
|
|
966
|
+
`अङ्गम् दिव् { … }` block form instead of nested calls.
|
|
967
|
+
3. **Type-ish annotations** drawn from Sanskrit grammatical categories.
|
|
968
|
+
4. **Editor support** — a TextMate grammar / VS Code extension highlighting the
|
|
969
|
+
keyword set in `keywords.js`.
|
|
970
|
+
5. **Sanskrit error messages** — partly done; expand `parser.js` diagnostics.
|
|
971
|
+
|
|
972
|
+
## License
|
|
973
|
+
|
|
974
|
+
MIT.
|