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/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "devabhasha",
3
+ "version": "1.0.0",
4
+ "description": "देवभाषा — a Sanskrit programming language for the web that transpiles to JavaScript",
5
+ "type": "module",
6
+ "main": "./src/index.js",
7
+ "exports": {
8
+ ".": "./src/index.js",
9
+ "./bundler": "./src/bundler.js",
10
+ "./cli": "./src/cli.js",
11
+ "./package.json": "./package.json"
12
+ },
13
+ "bin": {
14
+ "devabhasha": "./src/cli.js"
15
+ },
16
+ "files": [
17
+ "src",
18
+ "README.md",
19
+ "LICENSE"
20
+ ],
21
+ "scripts": {
22
+ "start": "node src/cli.js",
23
+ "test": "node test/test.js"
24
+ },
25
+ "keywords": [
26
+ "sanskrit",
27
+ "transpiler",
28
+ "compiler",
29
+ "devanagari",
30
+ "programming-language",
31
+ "javascript",
32
+ "devabhasha"
33
+ ],
34
+ "author": "Shyamu Parihar <pariharshyamu@gmail.com>",
35
+ "license": "MIT",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/pariharshyamu/devabhasha.git"
39
+ },
40
+ "homepage": "https://github.com/pariharshyamu/devabhasha#readme",
41
+ "bugs": {
42
+ "url": "https://github.com/pariharshyamu/devabhasha/issues"
43
+ },
44
+ "engines": {
45
+ "node": ">=18"
46
+ }
47
+ }
@@ -0,0 +1,125 @@
1
+ // analyzer.js — the language-analysis core powering the language server.
2
+ //
3
+ // Pure functions over source text: diagnostics (compile errors with
4
+ // positions), completions (keywords, stdlib, style vocab, tags), and hover
5
+ // (what a Sanskrit word means/translates to). The LSP server (server.js)
6
+ // is a thin protocol wrapper around these.
7
+
8
+ import { tokenize } from './lexer.js';
9
+ import { parse } from './parser.js';
10
+ import { generate } from './codegen.js';
11
+ import { DevabhashaError } from './errors.js';
12
+ import { KEYWORDS } from './keywords.js';
13
+ import { METHODS, PROPERTIES, MATH_CONSTANTS, GLOBALS } from './stdlib.js';
14
+ import { STYLE_PROPS, STYLE_VALUES } from './style.js';
15
+ import { TAG_STEMS, EVENT_STEMS } from './karaka-web.js';
16
+
17
+ // ---- diagnostics ----
18
+ // Returns an array of { line, col, endCol, message, severity } for a source.
19
+ // Empty array means the program compiles. Positions are 1-based.
20
+ export function diagnostics(source) {
21
+ try {
22
+ generate(parse(tokenize(source)), { includeRuntime: false });
23
+ return [];
24
+ } catch (e) {
25
+ if (e instanceof DevabhashaError) {
26
+ const line = e.line || 1;
27
+ const col = e.col || 1;
28
+ return [{
29
+ line, col, endCol: col + 1,
30
+ message: e.message,
31
+ kind: e.kind || 'parse',
32
+ severity: 1, // Error
33
+ }];
34
+ }
35
+ // unknown error — still surface it
36
+ return [{ line: 1, col: 1, endCol: 2, message: String(e.message || e), kind: 'internal', severity: 1 }];
37
+ }
38
+ }
39
+
40
+ // ---- the vocabulary index (for completion + hover) ----
41
+ // Each entry: { label, kind, detail, doc }
42
+ function buildVocabulary() {
43
+ const items = [];
44
+ const add = (label, kind, detail, doc) => items.push({ label, kind, detail, doc });
45
+
46
+ // keywords
47
+ const KW_DOC = {
48
+ 'चर': 'let — mutable variable', 'नियत': 'const — constant',
49
+ 'कार्य': 'function', 'फलम्': 'return', 'यदि': 'if', 'अन्यथा': 'else',
50
+ 'यावत्': 'while', 'प्रत्येकम्': 'for-of loop', 'भङ्ग': 'break',
51
+ 'अनुवृत्तम्': 'continue', 'सत्यम्': 'true', 'असत्यम्': 'false',
52
+ 'शून्यम्': 'null', 'दर्शय': 'print / console.log', 'कोष': 'object literal',
53
+ 'रचय': 'construct a DOM element', 'योजय': 'mount / append',
54
+ 'रूप': 'style block (CSS)', 'रूपनाम': 'named reusable style',
55
+ 'भाव': 'reactive state cell', 'दृश्य': 'reactive view region',
56
+ 'निर्यात': 'export', 'आयात': 'import', 'आ': 'from (module source)',
57
+ 'असमकालिक': 'async function', 'प्रतीक्षा': 'await', 'अथवा': 'Result value-or-fallback (or-else)',
58
+ };
59
+ for (const k of Object.keys(KEYWORDS)) add(k, 'keyword', KW_DOC[k] || 'keyword', KW_DOC[k] || '');
60
+
61
+ // stdlib methods, properties, math, globals
62
+ for (const [k, v] of Object.entries(METHODS)) add(k, 'method', `.${v}()`, `method → ${v}`);
63
+ for (const [k, v] of Object.entries(PROPERTIES)) add(k, 'property', `.${v}`, `property → ${v}`);
64
+ for (const [k, v] of Object.entries(MATH_CONSTANTS)) add(k, 'constant', `गणित.${v}`, `Math.${v}`);
65
+ for (const [k, v] of Object.entries(GLOBALS)) add(k, 'function', v, `builtin → ${v}`);
66
+
67
+ // style vocabulary
68
+ for (const [k, v] of Object.entries(STYLE_PROPS)) add(k, 'field', `${v}:`, `CSS property → ${v}`);
69
+ for (const [k, v] of Object.entries(STYLE_VALUES)) add(k, 'value', v, `CSS value → ${v}`);
70
+
71
+ // tags & events (the stem → element kind)
72
+ for (const [k, v] of Object.entries(TAG_STEMS)) add(k, 'tag', `<${v}>`, `element → <${v}>`);
73
+ for (const [k, v] of Object.entries(EVENT_STEMS)) add(k, 'event', v, `event → ${v}`);
74
+
75
+ return items;
76
+ }
77
+ const VOCAB = buildVocabulary();
78
+
79
+ // fast lookup by exact label (last write wins is fine; most are unique)
80
+ const VOCAB_BY_LABEL = new Map();
81
+ for (const it of VOCAB) if (!VOCAB_BY_LABEL.has(it.label)) VOCAB_BY_LABEL.set(it.label, it);
82
+
83
+ // ---- completion ----
84
+ // Given a source and a partial word (the token being typed), return matching
85
+ // vocabulary entries. If prefix is empty, returns everything (the client
86
+ // usually filters further).
87
+ export function completions(prefix = '') {
88
+ if (!prefix) return VOCAB.slice();
89
+ return VOCAB.filter(it => it.label.startsWith(prefix));
90
+ }
91
+
92
+ // Extract the "word" (Devanagari run) at a 0-based character offset in a line.
93
+ export function wordAt(lineText, charIndex) {
94
+ // Devanagari block + danda-free identifier chars
95
+ const isWordChar = ch => /[\u0900-\u097F_a-zA-Z0-9]/.test(ch);
96
+ let start = charIndex, end = charIndex;
97
+ while (start > 0 && isWordChar(lineText[start - 1])) start--;
98
+ while (end < lineText.length && isWordChar(lineText[end])) end++;
99
+ return { word: lineText.slice(start, end), start, end };
100
+ }
101
+
102
+ // ---- hover ----
103
+ // Return a doc string for a Sanskrit word, or null if unknown.
104
+ export function hover(word) {
105
+ const it = VOCAB_BY_LABEL.get(word);
106
+ if (!it) return null;
107
+ return { label: word, detail: it.detail, doc: it.doc, kind: it.kind };
108
+ }
109
+
110
+ export { VOCAB };
111
+
112
+ // ---- go-to-definition & rename (scope-aware, via the symbol table) ----
113
+ import { definitionAt, occurrencesAt } from './symbols.js';
114
+
115
+ // Where is the symbol at (line, col) defined? → { line, col, name } | null
116
+ export function definition(source, line, col) {
117
+ const b = definitionAt(source, line, col);
118
+ return b ? { line: b.line, col: b.col, name: b.name } : null;
119
+ }
120
+
121
+ // Every occurrence to rename for the symbol at (line, col).
122
+ // Returns [{ line, col, name }] (the binding plus all bound references).
123
+ export function renameOccurrences(source, line, col) {
124
+ return occurrencesAt(source, line, col);
125
+ }
package/src/bundler.js ADDED
@@ -0,0 +1,129 @@
1
+ // bundler.js — compile-time module resolution & linking (आयात / निर्यात).
2
+ //
3
+ // Design (the Rust/Python lineage): resolve the import graph at compile time,
4
+ // compile each .deva module to JS, wrap each in an IIFE that returns an object
5
+ // of its निर्यात exports, order them so dependencies come first, and link
6
+ // imports to the right module's exports. One self-contained bundle is emitted;
7
+ // the DOM runtime is included once for the whole program.
8
+
9
+ import { readFileSync, existsSync } from 'fs';
10
+ import { dirname, resolve, isAbsolute } from 'path';
11
+ import { compileModule, generate, PRELUDE } from './index.js';
12
+ import { tokenize } from './lexer.js';
13
+ import { parse } from './parser.js';
14
+ import { DevabhashaError } from './errors.js';
15
+ import { id } from './codegen.js';
16
+
17
+ // Resolve a module source string (as written in आयात "...") to an absolute
18
+ // path, relative to the importing file. Appends .deva when no extension.
19
+ function resolveSource(source, fromFile) {
20
+ let p = source;
21
+ if (!/\.deva$/.test(p)) p += '.deva';
22
+ const base = isAbsolute(p) ? p : resolve(dirname(fromFile), p);
23
+ return base;
24
+ }
25
+
26
+ // Build the dependency graph by walking आयात edges from the entry file.
27
+ // Returns { modules: Map<path, {path, code, exports, imports}>, order: [path] }
28
+ // where order is a topological sort (dependencies first). Cycles are tolerated
29
+ // (a module already on the stack is simply not re-entered).
30
+ export function buildGraph(entryPath) {
31
+ const modules = new Map();
32
+ const order = [];
33
+ const visiting = new Set();
34
+
35
+ function visit(absPath, importerPath) {
36
+ if (modules.has(absPath)) return; // already compiled
37
+ if (visiting.has(absPath)) return; // cycle — break it
38
+ if (!existsSync(absPath)) {
39
+ throw new DevabhashaError(
40
+ `आयातदोषः: module not found: ${absPath}` + (importerPath ? ` (imported by ${importerPath})` : ''),
41
+ { kind: 'parse' }
42
+ );
43
+ }
44
+ visiting.add(absPath);
45
+
46
+ const source = readFileSync(absPath, 'utf8');
47
+ let mod;
48
+ try {
49
+ mod = compileModule(source);
50
+ } catch (e) {
51
+ if (e instanceof DevabhashaError) { e.source = source; e.file = absPath; }
52
+ throw e;
53
+ }
54
+ mod.path = absPath;
55
+ mod.source = source;
56
+
57
+ // resolve each import's source path and recurse (dependencies first)
58
+ for (const imp of mod.imports) {
59
+ imp.resolved = resolveSource(imp.source, absPath);
60
+ visit(imp.resolved, absPath);
61
+ }
62
+
63
+ visiting.delete(absPath);
64
+ modules.set(absPath, mod);
65
+ order.push(absPath); // post-order = deps first
66
+ }
67
+
68
+ visit(resolve(entryPath), null);
69
+ return { modules, order };
70
+ }
71
+
72
+ // A stable JS identifier for a module path.
73
+ let __counter = 0;
74
+ const slotNames = new Map();
75
+ function moduleSlot(path) {
76
+ if (!slotNames.has(path)) slotNames.set(path, `__mod_${__counter++}`);
77
+ return slotNames.get(path);
78
+ }
79
+
80
+ // Link the graph into one JS program.
81
+ // includeRuntime: prepend the DOM runtime (for build); omit for run.
82
+ export function bundle(entryPath, { includeRuntime = true } = {}) {
83
+ __counter = 0; slotNames.clear();
84
+ const { modules, order } = buildGraph(entryPath);
85
+ const entryAbs = resolve(entryPath);
86
+
87
+ const pieces = [];
88
+ if (includeRuntime) {
89
+ // full runtime (PRELUDE + __DB) via a trivial program compiled with it
90
+ pieces.push(generate(parse(tokenize('')), { includeRuntime: true }));
91
+ } else {
92
+ // host-independent prelude is always needed (Result constructors etc.)
93
+ pieces.push(PRELUDE);
94
+ }
95
+
96
+ for (const path of order) {
97
+ const mod = modules.get(path);
98
+ const slot = moduleSlot(path);
99
+ const isEntry = path === entryAbs;
100
+
101
+ // build the import-binding prelude for this module
102
+ let prelude = '';
103
+ for (const imp of mod.imports) {
104
+ const depSlot = moduleSlot(imp.resolved);
105
+ if (imp.kind === 'namespace') {
106
+ prelude += `const ${id(imp.alias)} = ${depSlot};\n`;
107
+ } else if (imp.kind === 'named') {
108
+ for (const n of imp.names) {
109
+ prelude += `const ${id(n)} = ${depSlot}[${JSON.stringify(id(n))}];\n`;
110
+ }
111
+ } // 'effect' imports need no bindings; the dep already ran
112
+ }
113
+
114
+ // the module's own code (no runtime — added once above)
115
+ const body = mod.code;
116
+
117
+ // export object: { exportedName: exportedName, ... }
118
+ const exportObj = '{ ' + mod.exports.map(n => `${JSON.stringify(id(n))}: ${id(n)}`).join(', ') + ' }';
119
+
120
+ if (isEntry) {
121
+ // entry runs in an IIFE too (so its imports get scoped bindings)
122
+ pieces.push(`const ${slot} = (function () {\n${prelude}${body}\nreturn ${exportObj};\n})();`);
123
+ } else {
124
+ pieces.push(`const ${slot} = (function () {\n${prelude}${body}\nreturn ${exportObj};\n})();`);
125
+ }
126
+ }
127
+
128
+ return pieces.join('\n\n');
129
+ }
package/src/cli.js ADDED
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env node
2
+ // cli.js — command line interface.
3
+ // devabhasha build file.deva [-o out.js] compile to JavaScript
4
+ // devabhasha run file.deva compile and execute with Node
5
+
6
+ import { readFileSync, writeFileSync } from 'fs';
7
+ import { compile, compileWithMap } from './index.js';
8
+ import { bundle } from './bundler.js';
9
+ import { serve } from './devserver.js';
10
+ import { __IO, IO_NODE_SOURCE } from './io-node.js';
11
+ import { __SRV, SRV_NODE_SOURCE } from './server-node.js';
12
+ import { DevabhashaError, formatError } from './errors.js';
13
+
14
+ const args = process.argv.slice(2);
15
+ const cmd = args[0];
16
+
17
+ function fail(msg) { console.error('दोषः: ' + msg); process.exit(1); }
18
+
19
+ // Print a compiler error with source context if available.
20
+ function failCompile(e, source) {
21
+ if (e instanceof DevabhashaError) {
22
+ console.error(formatError(e, e.source || source));
23
+ } else {
24
+ console.error('दोषः: ' + e.message);
25
+ }
26
+ process.exit(1);
27
+ }
28
+
29
+ if (!cmd || !['build', 'run', 'serve'].includes(cmd)) {
30
+ console.log(`देवभाषा — Sanskrit → JavaScript transpiler
31
+
32
+ Usage:
33
+ devabhasha build <file.deva> [-o out.js] compile (resolves आयात) to JavaScript
34
+ devabhasha run <file.deva> compile and run with Node
35
+ devabhasha serve <file.deva> [--port N] dev server with live reload (web)
36
+ `);
37
+ process.exit(0);
38
+ }
39
+
40
+ const file = args[1];
41
+ if (!file) fail('no input file');
42
+
43
+ if (cmd === 'serve') {
44
+ const portIdx = args.indexOf('--port');
45
+ const port = portIdx >= 0 ? parseInt(args[portIdx + 1], 10) || 5173 : 5173;
46
+ try { readFileSync(file, 'utf8'); } catch { fail(`cannot read ${file}`); }
47
+ serve(file, { port });
48
+ // keep the process alive (server + watcher)
49
+ } else {
50
+
51
+ let src;
52
+ try { src = readFileSync(file, 'utf8'); }
53
+ catch { fail(`cannot read ${file}`); }
54
+
55
+ if (cmd === 'build') {
56
+ const wantMap = args.includes('--sourcemap') || args.includes('-m');
57
+ const outIdx = args.indexOf('-o');
58
+ const out = outIdx >= 0 ? args[outIdx + 1] : file.replace(/\.deva$/, '.js');
59
+
60
+ // --sourcemap uses the single-file compiler (with positions); without it,
61
+ // the multi-file bundler (which resolves आयात) is used.
62
+ if (wantMap) {
63
+ let result;
64
+ try {
65
+ result = compileWithMap(src, { includeRuntime: true });
66
+ } catch (e) { failCompile(e, src); }
67
+ const mapFile = out + '.map';
68
+ result.map.file = out.split('/').pop();
69
+ result.map.sources = [file.split('/').pop()];
70
+ result.map.sourcesContent = [src];
71
+ writeFileSync(out, result.code + `\n//# sourceMappingURL=${mapFile.split('/').pop()}\n`);
72
+ writeFileSync(mapFile, JSON.stringify(result.map));
73
+ console.log(`✓ ${out}`);
74
+ console.log(`✓ ${mapFile}`);
75
+ } else {
76
+ let js;
77
+ try { js = bundle(file, { includeRuntime: true }); }
78
+ catch (e) { failCompile(e, src); }
79
+ // inline the Node I/O + HTTP server backends so the built .js is self-contained
80
+ writeFileSync(out, IO_NODE_SOURCE + '\n' + SRV_NODE_SOURCE + '\n' + js);
81
+ console.log(`✓ ${out}`);
82
+ }
83
+ } else {
84
+ // run: bundle without the DOM runtime, then execute with the Node I/O
85
+ // backend injected and a stub __DB (DOM needs a browser).
86
+ let runnable;
87
+ try { runnable = bundle(file, { includeRuntime: false }); }
88
+ catch (e) { failCompile(e, src); }
89
+ const __DB = new Proxy({}, { get: () => () => {
90
+ throw new Error('DOM operations need a browser — use the playground or build the JS.');
91
+ }});
92
+ try {
93
+ // eslint-disable-next-line no-new-func
94
+ new Function('__DB', '__IO', '__SRV', runnable)(__DB, __IO, __SRV);
95
+ } catch (e) {
96
+ fail('runtime: ' + e.message);
97
+ }
98
+ }
99
+ }