ga-lang 1.2.1 → 1.2.4

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.
@@ -0,0 +1,77 @@
1
+ import type { Token } from './token.js';
2
+ export interface Expr {
3
+ accept<T>(visitor: ExprVisitor<T>): T;
4
+ }
5
+ export interface ExprVisitor<T> {
6
+ visitVariableExpr(expr: VariableExpr): T;
7
+ visitLiteralExpr(expr: LiteralExpr): T;
8
+ visitCallExpr(expr: CallExpr): T;
9
+ visitBinaryExpr(expr: BinaryExpr): T;
10
+ }
11
+ export interface Stmt {
12
+ accept<T>(visitor: StmtVisitor<T>): T;
13
+ }
14
+ export interface StmtVisitor<T> {
15
+ visitPrintStmt(stmt: PrintStmt): T;
16
+ visitVarStmt(stmt: VarStmt): T;
17
+ visitFunctionStmt(stmt: FunctionStmt): T;
18
+ visitBlockStmt(stmt: BlockStmt): T;
19
+ visitExpressionStmt(stmt: ExpressionStmt): T;
20
+ visitReturnStmt(stmt: ReturnStmt): T;
21
+ }
22
+ export declare class VariableExpr implements Expr {
23
+ name: Token;
24
+ constructor(name: Token);
25
+ accept<T>(visitor: ExprVisitor<T>): T;
26
+ }
27
+ export declare class LiteralExpr implements Expr {
28
+ value: any;
29
+ constructor(value: any);
30
+ accept<T>(visitor: ExprVisitor<T>): T;
31
+ }
32
+ export declare class CallExpr implements Expr {
33
+ callee: VariableExpr;
34
+ args: Expr[];
35
+ constructor(callee: VariableExpr, args: Expr[]);
36
+ accept<T>(visitor: ExprVisitor<T>): T;
37
+ }
38
+ export declare class BinaryExpr implements Expr {
39
+ left: Expr;
40
+ operator: Token;
41
+ right: Expr;
42
+ constructor(left: Expr, operator: Token, right: Expr);
43
+ accept<T>(visitor: ExprVisitor<T>): T;
44
+ }
45
+ export declare class PrintStmt implements Stmt {
46
+ expression: Expr;
47
+ constructor(expression: Expr);
48
+ accept<T>(visitor: StmtVisitor<T>): T;
49
+ }
50
+ export declare class VarStmt implements Stmt {
51
+ name: Token;
52
+ initializer: Expr | null;
53
+ constructor(name: Token, initializer: Expr | null);
54
+ accept<T>(visitor: StmtVisitor<T>): T;
55
+ }
56
+ export declare class FunctionStmt implements Stmt {
57
+ name: Token;
58
+ params: Token[];
59
+ body: Stmt[];
60
+ constructor(name: Token, params: Token[], body: Stmt[]);
61
+ accept<T>(visitor: StmtVisitor<T>): T;
62
+ }
63
+ export declare class BlockStmt implements Stmt {
64
+ statements: Stmt[];
65
+ constructor(statements: Stmt[]);
66
+ accept<T>(visitor: StmtVisitor<T>): T;
67
+ }
68
+ export declare class ExpressionStmt implements Stmt {
69
+ expression: Expr;
70
+ constructor(expression: Expr);
71
+ accept<T>(visitor: StmtVisitor<T>): T;
72
+ }
73
+ export declare class ReturnStmt implements Stmt {
74
+ value: Expr | null;
75
+ constructor(value: Expr | null);
76
+ accept<T>(visitor: StmtVisitor<T>): T;
77
+ }
package/dist/src/ast.js CHANGED
@@ -27,6 +27,19 @@ export class CallExpr {
27
27
  return visitor.visitCallExpr(this);
28
28
  }
29
29
  }
30
+ export class BinaryExpr {
31
+ left;
32
+ operator;
33
+ right;
34
+ constructor(left, operator, right) {
35
+ this.left = left;
36
+ this.operator = operator;
37
+ this.right = right;
38
+ }
39
+ accept(visitor) {
40
+ return visitor.visitBinaryExpr(this);
41
+ }
42
+ }
30
43
  export class PrintStmt {
31
44
  expression;
32
45
  constructor(expression) {
@@ -78,3 +91,12 @@ export class ExpressionStmt {
78
91
  return visitor.visitExpressionStmt(this);
79
92
  }
80
93
  }
94
+ export class ReturnStmt {
95
+ value;
96
+ constructor(value) {
97
+ this.value = value;
98
+ }
99
+ accept(visitor) {
100
+ return visitor.visitReturnStmt(this);
101
+ }
102
+ }
@@ -0,0 +1,22 @@
1
+ import { BinaryExpr, FunctionStmt, ReturnStmt, type BlockStmt, type CallExpr, type ExpressionStmt, type ExprVisitor, type LiteralExpr, type PrintStmt, type Stmt, type StmtVisitor, type VariableExpr, type VarStmt } from './ast.js';
2
+ export declare class Interpreter implements ExprVisitor<any>, StmtVisitor<void> {
3
+ private environment;
4
+ interpret(statements: Stmt[]): void;
5
+ private execute;
6
+ visitPrintStmt(stmt: PrintStmt): void;
7
+ private toDevanagariString;
8
+ private numberToDevanagari;
9
+ visitVarStmt(stmt: VarStmt): void;
10
+ visitFunctionStmt(stmt: FunctionStmt): void;
11
+ visitBlockStmt(stmt: BlockStmt): void;
12
+ visitExpressionStmt(stmt: ExpressionStmt): void;
13
+ visitReturnStmt(stmt: ReturnStmt): void;
14
+ visitLiteralExpr(expr: LiteralExpr): any;
15
+ private isDevanagariNumber;
16
+ private devanagariToNumber;
17
+ visitVariableExpr(expr: VariableExpr): any;
18
+ visitCallExpr(expr: CallExpr): any;
19
+ visitBinaryExpr(expr: BinaryExpr): any;
20
+ private toNumber;
21
+ private evaluate;
22
+ }
@@ -1,4 +1,12 @@
1
- import { FunctionStmt } from './ast.js';
1
+ import { BinaryExpr, FunctionStmt, ReturnStmt } from './ast.js';
2
+ import { TokenKind } from './token.js';
3
+ class ReturnValue extends Error {
4
+ value;
5
+ constructor(value) {
6
+ super();
7
+ this.value = value;
8
+ }
9
+ }
2
10
  export class Interpreter {
3
11
  environment = new Map();
4
12
  interpret(statements) {
@@ -14,7 +22,26 @@ export class Interpreter {
14
22
  */
15
23
  visitPrintStmt(stmt) {
16
24
  const value = this.evaluate(stmt.expression);
17
- console.log(value);
25
+ console.log(this.toDevanagariString(value));
26
+ }
27
+ toDevanagariString(value) {
28
+ if (typeof value === 'number') {
29
+ return this.numberToDevanagari(value);
30
+ }
31
+ return String(value);
32
+ }
33
+ numberToDevanagari(num) {
34
+ const devanagariZero = 0x0966;
35
+ return num
36
+ .toString()
37
+ .split('')
38
+ .map((char) => {
39
+ if (char >= '0' && char <= '9') {
40
+ return String.fromCharCode(devanagariZero + parseInt(char));
41
+ }
42
+ return char;
43
+ })
44
+ .join('');
18
45
  }
19
46
  visitVarStmt(stmt) {
20
47
  const value = stmt.initializer !== null ? this.evaluate(stmt.initializer) : null;
@@ -31,12 +58,40 @@ export class Interpreter {
31
58
  visitExpressionStmt(stmt) {
32
59
  this.evaluate(stmt.expression);
33
60
  }
61
+ visitReturnStmt(stmt) {
62
+ const value = stmt.value !== null ? this.evaluate(stmt.value) : null;
63
+ throw new ReturnValue(value);
64
+ }
34
65
  visitLiteralExpr(expr) {
35
66
  if (typeof expr.value === 'string' && expr.value.startsWith('"')) {
36
67
  return expr.value.slice(1, -1);
37
68
  }
69
+ // Check if it's a Devanagari number and convert it
70
+ if (typeof expr.value === 'string' && this.isDevanagariNumber(expr.value)) {
71
+ return this.devanagariToNumber(expr.value);
72
+ }
38
73
  return expr.value;
39
74
  }
75
+ isDevanagariNumber(value) {
76
+ if (value.length === 0)
77
+ return false;
78
+ for (const char of value) {
79
+ const code = char.charCodeAt(0);
80
+ if (code < 0x0966 || code > 0x096f) {
81
+ return false;
82
+ }
83
+ }
84
+ return true;
85
+ }
86
+ devanagariToNumber(value) {
87
+ const devanagariZero = 0x0966;
88
+ let num = 0;
89
+ for (const char of value) {
90
+ const code = char.charCodeAt(0);
91
+ num = num * 10 + (code - devanagariZero);
92
+ }
93
+ return num;
94
+ }
40
95
  visitVariableExpr(expr) {
41
96
  const value = this.environment.get(expr.name.lexeme);
42
97
  if (value === undefined) {
@@ -64,13 +119,85 @@ export class Interpreter {
64
119
  this.environment.set(func.params[i].lexeme, value);
65
120
  }
66
121
  // Execute function body
67
- for (const stmt of func.body) {
68
- this.execute(stmt);
122
+ try {
123
+ for (const stmt of func.body) {
124
+ this.execute(stmt);
125
+ }
126
+ }
127
+ catch (returnValue) {
128
+ if (returnValue instanceof ReturnValue) {
129
+ // Restore previous environment before returning
130
+ this.environment = prevEnvironment;
131
+ return returnValue.value;
132
+ }
133
+ throw returnValue;
69
134
  }
70
135
  // Restore previous environment
71
136
  this.environment = prevEnvironment;
72
137
  return null;
73
138
  }
139
+ visitBinaryExpr(expr) {
140
+ const left = this.evaluate(expr.left);
141
+ const right = this.evaluate(expr.right);
142
+ // Convert Devanagari digits to regular numbers for arithmetic
143
+ const leftNum = this.toNumber(left);
144
+ const rightNum = this.toNumber(right);
145
+ switch (expr.operator.kind) {
146
+ case TokenKind.Plus:
147
+ // Handle string concatenation or numeric addition
148
+ if (typeof left === 'string' && typeof right === 'string') {
149
+ return left + right;
150
+ }
151
+ return leftNum + rightNum;
152
+ case TokenKind.Minus:
153
+ return leftNum - rightNum;
154
+ case TokenKind.Star:
155
+ return leftNum * rightNum;
156
+ case TokenKind.Slash:
157
+ if (rightNum === 0) {
158
+ throw new Error('Division by zero');
159
+ }
160
+ return leftNum / rightNum;
161
+ case TokenKind.Mod:
162
+ if (rightNum === 0) {
163
+ throw new Error('Modulo by zero');
164
+ }
165
+ return leftNum % rightNum;
166
+ default:
167
+ throw new Error(`Unknown operator: ${expr.operator.kind}`);
168
+ }
169
+ }
170
+ toNumber(value) {
171
+ if (typeof value === 'number')
172
+ return value;
173
+ if (typeof value === 'string') {
174
+ // Check if it's a Devanagari number
175
+ const devanagariZero = '\u{0966}'.charCodeAt(0); // ०
176
+ let isDevanagari = true;
177
+ let num = 0;
178
+ for (const char of value) {
179
+ const code = char.charCodeAt(0);
180
+ if (code >= devanagariZero && code <= devanagariZero + 9) {
181
+ num = num * 10 + (code - devanagariZero);
182
+ }
183
+ else if (char >= '0' && char <= '9') {
184
+ isDevanagari = false;
185
+ }
186
+ else {
187
+ throw new Error(`Cannot convert '${value}' to number`);
188
+ }
189
+ }
190
+ if (isDevanagari && value.length > 0)
191
+ return num;
192
+ // Otherwise try regular parsing
193
+ const parsed = parseFloat(value);
194
+ if (isNaN(parsed)) {
195
+ throw new Error(`Cannot convert '${value}' to number`);
196
+ }
197
+ return parsed;
198
+ }
199
+ throw new Error(`Cannot convert '${value}' to number`);
200
+ }
74
201
  /*
75
202
  * Evaluate an expression
76
203
  */
@@ -0,0 +1,66 @@
1
+ import { Token, TokenKind } from './token.js';
2
+ export declare class Lexer {
3
+ private source;
4
+ private tokens;
5
+ private startPos;
6
+ private currentPos;
7
+ private line;
8
+ constructor(source: string);
9
+ /**
10
+ * Read tokens from source
11
+ */
12
+ readTokens(): Token[];
13
+ /**
14
+ * Read individual lexeme
15
+ */
16
+ readToken(): void;
17
+ /**
18
+ * Read character
19
+ */
20
+ readChar(): string;
21
+ /**
22
+ * Create token out of individual lexeme
23
+ * @param {TokenKind} kind
24
+ * @param {any} literal
25
+ */
26
+ createToken(kind: TokenKind, literal?: any): void;
27
+ /**
28
+ * Check if we've reached the end of file
29
+ */
30
+ private isAtEnd;
31
+ /**
32
+ * Peek next character
33
+ */
34
+ private peekNextChar;
35
+ /**
36
+ * Get next character
37
+ */
38
+ private getNextChar;
39
+ /**
40
+ * Check if the character is devanagari character
41
+ * @param {string} char
42
+ */
43
+ private isDevnagariChar;
44
+ /**
45
+ * Check if the character is devanagari digit
46
+ * @param {string} char
47
+ */
48
+ private isDevanagariDigit;
49
+ /**
50
+ * Check if the character is whitespace
51
+ * @param {string} char
52
+ */
53
+ private isWhitespace;
54
+ /**
55
+ * Read devanagari character sequence as number
56
+ */
57
+ private readDevanagariDigit;
58
+ /**
59
+ * Read devanagari character sequence as identifier
60
+ */
61
+ private readDevanagariIdentifier;
62
+ /**
63
+ * Read string literals
64
+ */
65
+ private readDevanagariString;
66
+ }
@@ -0,0 +1,4 @@
1
+ import { Lexer } from './lexer.js';
2
+ import { Parser } from './parser.js';
3
+ import { Interpreter } from './interpreter.js';
4
+ export { Lexer, Parser, Interpreter };
package/dist/src/main.js CHANGED
@@ -28,3 +28,5 @@ function main() {
28
28
  }
29
29
  }
30
30
  main();
31
+ // Export Lexer, Parser, and Interpreter
32
+ export { Lexer, Parser, Interpreter };
@@ -0,0 +1,28 @@
1
+ import { type Stmt } from './ast.js';
2
+ import { type Token } from './token.js';
3
+ export declare class Parser {
4
+ private tokens;
5
+ private currentPos;
6
+ constructor(tokens: Token[]);
7
+ parse(): Stmt[];
8
+ private parseStmt;
9
+ private parseReturnStmt;
10
+ private parseExpressionStmt;
11
+ private parsePrintStmt;
12
+ private parseVarStmt;
13
+ private parseFunctionStmt;
14
+ private parseBlock;
15
+ private parseExpression;
16
+ private parseAddition;
17
+ private parseMultiplication;
18
+ private parseCall;
19
+ private parsePrimary;
20
+ private match;
21
+ private consume;
22
+ private check;
23
+ private advance;
24
+ private peek;
25
+ private previous;
26
+ private isAtEnd;
27
+ private error;
28
+ }
@@ -1,4 +1,4 @@
1
- import { CallExpr, ExpressionStmt, FunctionStmt, LiteralExpr, PrintStmt, VarStmt, VariableExpr } from './ast.js';
1
+ import { BinaryExpr, CallExpr, ExpressionStmt, FunctionStmt, LiteralExpr, PrintStmt, ReturnStmt, VarStmt, VariableExpr } from './ast.js';
2
2
  import { TokenKind } from './token.js';
3
3
  export class Parser {
4
4
  tokens;
@@ -28,8 +28,19 @@ export class Parser {
28
28
  if (this.match(TokenKind.Function)) {
29
29
  return this.parseFunctionStmt();
30
30
  }
31
+ if (this.match(TokenKind.Return)) {
32
+ return this.parseReturnStmt();
33
+ }
31
34
  return this.parseExpressionStmt();
32
35
  }
36
+ parseReturnStmt() {
37
+ let value = null;
38
+ // Check if there's a value to return (not just "फिर्ता" alone)
39
+ if (!this.check(TokenKind.CloseCurly) && !this.isAtEnd()) {
40
+ value = this.parseExpression();
41
+ }
42
+ return new ReturnStmt(value);
43
+ }
33
44
  parseExpressionStmt() {
34
45
  const expr = this.parseExpression();
35
46
  return new ExpressionStmt(expr);
@@ -71,7 +82,25 @@ export class Parser {
71
82
  return statements;
72
83
  }
73
84
  parseExpression() {
74
- return this.parseCall();
85
+ return this.parseAddition();
86
+ }
87
+ parseAddition() {
88
+ let expr = this.parseMultiplication();
89
+ while (this.match(TokenKind.Plus, TokenKind.Minus)) {
90
+ const operator = this.previous();
91
+ const right = this.parseMultiplication();
92
+ expr = new BinaryExpr(expr, operator, right);
93
+ }
94
+ return expr;
95
+ }
96
+ parseMultiplication() {
97
+ let expr = this.parseCall();
98
+ while (this.match(TokenKind.Star, TokenKind.Slash, TokenKind.Mod)) {
99
+ const operator = this.previous();
100
+ const right = this.parseCall();
101
+ expr = new BinaryExpr(expr, operator, right);
102
+ }
103
+ return expr;
75
104
  }
76
105
  parseCall() {
77
106
  let expr = this.parsePrimary();
@@ -99,6 +128,11 @@ export class Parser {
99
128
  if (this.match(TokenKind.Identifier)) {
100
129
  return new VariableExpr(this.previous());
101
130
  }
131
+ if (this.match(TokenKind.OpenParen)) {
132
+ const expr = this.parseExpression();
133
+ this.consume(TokenKind.CloseParen, "Expect ')' after expression");
134
+ return expr;
135
+ }
102
136
  throw this.error(this.peek(), 'Expression expected.');
103
137
  }
104
138
  match(...kinds) {
@@ -0,0 +1,38 @@
1
+ export declare enum TokenKind {
2
+ Eof = "Eof",
3
+ Illegal = "Illegal",
4
+ Comma = "Comma",
5
+ Colon = "Colon",
6
+ Period = "Period",
7
+ Semicolon = "Semicolon",
8
+ OpenParen = "OpenParen",
9
+ CloseParen = "CloseParen",
10
+ OpenCurly = "OpenCurly",
11
+ CloseCurly = "CloseCurly",
12
+ Plus = "Plus",
13
+ Minus = "Minus",
14
+ Star = "Star",
15
+ Slash = "Slash",
16
+ Mod = "Mod",
17
+ Bang = "Bang",
18
+ Equal = "Equal",
19
+ Number = "Number",
20
+ Character = "Character",
21
+ String = "String",
22
+ Identifier = "Identifier",
23
+ Let = "Let",
24
+ Function = "Function",
25
+ Print = "Print",
26
+ Return = "Return",
27
+ If = "If",
28
+ Else = "Else",
29
+ True = "True",
30
+ False = "False"
31
+ }
32
+ export declare const keywords: Record<string, TokenKind>;
33
+ export declare class Token {
34
+ kind: TokenKind;
35
+ lexeme: string;
36
+ line: number;
37
+ constructor(kind: TokenKind, lexeme: string, line: number);
38
+ }
package/examples/init.ga CHANGED
@@ -1,14 +1,6 @@
1
- कार्य सुरु () {
2
- मानौ सन्देश = "सोच्छौ के मेरो बारे?"
3
- मानौ एकदुइतिन = १२३
4
- छाप(सन्देश)
5
- छाप(एकदुइतिन)
1
+ कार्य जोड (अ , आ) {
2
+ फिर्ता +
6
3
  }
7
4
 
8
- कार्य जोड( अ, आ) {
9
- छाप()
10
- छाप(आ)
11
- }
12
-
13
- सुरु()
14
- जोड(१,२)
5
+ मानौ जम्मा = जोड(१, )
6
+ छाप(जम्मा)
package/package.json CHANGED
@@ -1,15 +1,25 @@
1
1
  {
2
2
  "name": "ga-lang",
3
- "version": "1.2.1",
3
+ "version": "1.2.4",
4
4
  "description": "An interpreted toy programming language",
5
5
  "type": "module",
6
- "main": "dist/main.js",
6
+ "main": "dist/src/main.js",
7
+ "types": "dist/src/main.d.ts",
7
8
  "bin": {
8
9
  "ga": "./bin/ga.js"
9
10
  },
11
+ "scripts": {
12
+ "prettier:check": "prettier --check \"./**/*.{js,ts,json}\"",
13
+ "prettier:fix": "prettier --write \"./**/*.{js,ts,json}\"",
14
+ "clean": "rm -rf dist",
15
+ "build": "pnpm run clean && tsc",
16
+ "build:tests": "tsc -p tsconfig.test.json",
17
+ "test": "pnpm run build && pnpm run build:tests && node --test ./dist/tests/**/*.js",
18
+ "ga": "pnpm run build && node dist/src/main.js"
19
+ },
10
20
  "repository": {
11
21
  "type": "git",
12
- "url": "git+https://github.com/bvsvntv/ga.git"
22
+ "url": "git+https://github.com/bvsvntv/ga-lang.git"
13
23
  },
14
24
  "keywords": [
15
25
  "devanagari",
@@ -20,13 +30,17 @@
20
30
  "author": "Basanta Rai",
21
31
  "license": "MIT",
22
32
  "bugs": {
23
- "url": "https://github.com/bvsvntv/ga/issues"
33
+ "url": "https://github.com/bvsvntv/ga-lang/issues"
24
34
  },
25
- "homepage": "https://github.com/bvsvntv/ga#readme",
35
+ "homepage": "https://github.com/bvsvntv/ga-lang#readme",
36
+ "packageManager": "pnpm@10.25.0",
26
37
  "engines": {
27
38
  "node": ">=22.x.x",
28
39
  "pnpm": ">=10.x.x"
29
40
  },
41
+ "exports": {
42
+ ".": "./dist/src/main.js"
43
+ },
30
44
  "devEngines": {
31
45
  "runtime": {
32
46
  "name": "node",
@@ -37,13 +51,5 @@
37
51
  "@types/node": "^25.0.3",
38
52
  "prettier": "^3.7.4",
39
53
  "typescript": "^5.9.3"
40
- },
41
- "scripts": {
42
- "prettier:check": "prettier --check \"./**/*.{js,ts,json}\"",
43
- "prettier:fix": "prettier --write \"./**/*.{js,ts,json}\"",
44
- "clean": "rm -rf dist",
45
- "build": "pnpm run clean && tsc",
46
- "test": "pnpm run build && node --test ./dist/tests/**/*.js",
47
- "ga": "pnpm run build && node dist/src/main.js"
48
54
  }
49
- }
55
+ }
package/src/ast.ts CHANGED
@@ -8,6 +8,7 @@ export interface ExprVisitor<T> {
8
8
  visitVariableExpr(expr: VariableExpr): T;
9
9
  visitLiteralExpr(expr: LiteralExpr): T;
10
10
  visitCallExpr(expr: CallExpr): T;
11
+ visitBinaryExpr(expr: BinaryExpr): T;
11
12
  }
12
13
 
13
14
  export interface Stmt {
@@ -20,6 +21,7 @@ export interface StmtVisitor<T> {
20
21
  visitFunctionStmt(stmt: FunctionStmt): T;
21
22
  visitBlockStmt(stmt: BlockStmt): T;
22
23
  visitExpressionStmt(stmt: ExpressionStmt): T;
24
+ visitReturnStmt(stmt: ReturnStmt): T;
23
25
  }
24
26
 
25
27
  export class VariableExpr implements Expr {
@@ -60,6 +62,22 @@ export class CallExpr implements Expr {
60
62
  }
61
63
  }
62
64
 
65
+ export class BinaryExpr implements Expr {
66
+ left: Expr;
67
+ operator: Token;
68
+ right: Expr;
69
+
70
+ constructor(left: Expr, operator: Token, right: Expr) {
71
+ this.left = left;
72
+ this.operator = operator;
73
+ this.right = right;
74
+ }
75
+
76
+ accept<T>(visitor: ExprVisitor<T>): T {
77
+ return visitor.visitBinaryExpr(this);
78
+ }
79
+ }
80
+
63
81
  export class PrintStmt implements Stmt {
64
82
  expression: Expr;
65
83
 
@@ -125,3 +143,15 @@ export class ExpressionStmt implements Stmt {
125
143
  return visitor.visitExpressionStmt(this);
126
144
  }
127
145
  }
146
+
147
+ export class ReturnStmt implements Stmt {
148
+ value: Expr | null;
149
+
150
+ constructor(value: Expr | null) {
151
+ this.value = value;
152
+ }
153
+
154
+ accept<T>(visitor: StmtVisitor<T>): T {
155
+ return visitor.visitReturnStmt(this);
156
+ }
157
+ }
@@ -1,5 +1,7 @@
1
1
  import {
2
+ BinaryExpr,
2
3
  FunctionStmt,
4
+ ReturnStmt,
3
5
  type BlockStmt,
4
6
  type CallExpr,
5
7
  type ExpressionStmt,
@@ -12,6 +14,15 @@ import {
12
14
  type VariableExpr,
13
15
  type VarStmt
14
16
  } from './ast.js';
17
+ import { TokenKind } from './token.js';
18
+
19
+ class ReturnValue extends Error {
20
+ value: any;
21
+ constructor(value: any) {
22
+ super();
23
+ this.value = value;
24
+ }
25
+ }
15
26
 
16
27
  export class Interpreter implements ExprVisitor<any>, StmtVisitor<void> {
17
28
  private environment: Map<string, any> = new Map();
@@ -31,7 +42,28 @@ export class Interpreter implements ExprVisitor<any>, StmtVisitor<void> {
31
42
  */
32
43
  visitPrintStmt(stmt: PrintStmt): void {
33
44
  const value = this.evaluate(stmt.expression);
34
- console.log(value);
45
+ console.log(this.toDevanagariString(value));
46
+ }
47
+
48
+ private toDevanagariString(value: any): string {
49
+ if (typeof value === 'number') {
50
+ return this.numberToDevanagari(value);
51
+ }
52
+ return String(value);
53
+ }
54
+
55
+ private numberToDevanagari(num: number): string {
56
+ const devanagariZero = 0x0966;
57
+ return num
58
+ .toString()
59
+ .split('')
60
+ .map((char) => {
61
+ if (char >= '0' && char <= '9') {
62
+ return String.fromCharCode(devanagariZero + parseInt(char));
63
+ }
64
+ return char;
65
+ })
66
+ .join('');
35
67
  }
36
68
 
37
69
  visitVarStmt(stmt: VarStmt): void {
@@ -54,14 +86,45 @@ export class Interpreter implements ExprVisitor<any>, StmtVisitor<void> {
54
86
  this.evaluate(stmt.expression);
55
87
  }
56
88
 
89
+ visitReturnStmt(stmt: ReturnStmt): void {
90
+ const value = stmt.value !== null ? this.evaluate(stmt.value) : null;
91
+ throw new ReturnValue(value);
92
+ }
93
+
57
94
  visitLiteralExpr(expr: LiteralExpr): any {
58
95
  if (typeof expr.value === 'string' && expr.value.startsWith('"')) {
59
96
  return expr.value.slice(1, -1);
60
97
  }
61
98
 
99
+ // Check if it's a Devanagari number and convert it
100
+ if (typeof expr.value === 'string' && this.isDevanagariNumber(expr.value)) {
101
+ return this.devanagariToNumber(expr.value);
102
+ }
103
+
62
104
  return expr.value;
63
105
  }
64
106
 
107
+ private isDevanagariNumber(value: string): boolean {
108
+ if (value.length === 0) return false;
109
+ for (const char of value) {
110
+ const code = char.charCodeAt(0);
111
+ if (code < 0x0966 || code > 0x096f) {
112
+ return false;
113
+ }
114
+ }
115
+ return true;
116
+ }
117
+
118
+ private devanagariToNumber(value: string): number {
119
+ const devanagariZero = 0x0966;
120
+ let num = 0;
121
+ for (const char of value) {
122
+ const code = char.charCodeAt(0);
123
+ num = num * 10 + (code - devanagariZero);
124
+ }
125
+ return num;
126
+ }
127
+
65
128
  visitVariableExpr(expr: VariableExpr): any {
66
129
  const value = this.environment.get(expr.name.lexeme);
67
130
  if (value === undefined) {
@@ -96,8 +159,17 @@ export class Interpreter implements ExprVisitor<any>, StmtVisitor<void> {
96
159
  }
97
160
 
98
161
  // Execute function body
99
- for (const stmt of func.body) {
100
- this.execute(stmt);
162
+ try {
163
+ for (const stmt of func.body) {
164
+ this.execute(stmt);
165
+ }
166
+ } catch (returnValue) {
167
+ if (returnValue instanceof ReturnValue) {
168
+ // Restore previous environment before returning
169
+ this.environment = prevEnvironment;
170
+ return returnValue.value;
171
+ }
172
+ throw returnValue;
101
173
  }
102
174
 
103
175
  // Restore previous environment
@@ -106,6 +178,68 @@ export class Interpreter implements ExprVisitor<any>, StmtVisitor<void> {
106
178
  return null;
107
179
  }
108
180
 
181
+ visitBinaryExpr(expr: BinaryExpr): any {
182
+ const left = this.evaluate(expr.left);
183
+ const right = this.evaluate(expr.right);
184
+
185
+ // Convert Devanagari digits to regular numbers for arithmetic
186
+ const leftNum = this.toNumber(left);
187
+ const rightNum = this.toNumber(right);
188
+
189
+ switch (expr.operator.kind) {
190
+ case TokenKind.Plus:
191
+ // Handle string concatenation or numeric addition
192
+ if (typeof left === 'string' && typeof right === 'string') {
193
+ return left + right;
194
+ }
195
+ return leftNum + rightNum;
196
+ case TokenKind.Minus:
197
+ return leftNum - rightNum;
198
+ case TokenKind.Star:
199
+ return leftNum * rightNum;
200
+ case TokenKind.Slash:
201
+ if (rightNum === 0) {
202
+ throw new Error('Division by zero');
203
+ }
204
+ return leftNum / rightNum;
205
+ case TokenKind.Mod:
206
+ if (rightNum === 0) {
207
+ throw new Error('Modulo by zero');
208
+ }
209
+ return leftNum % rightNum;
210
+ default:
211
+ throw new Error(`Unknown operator: ${expr.operator.kind}`);
212
+ }
213
+ }
214
+
215
+ private toNumber(value: any): number {
216
+ if (typeof value === 'number') return value;
217
+ if (typeof value === 'string') {
218
+ // Check if it's a Devanagari number
219
+ const devanagariZero = '\u{0966}'.charCodeAt(0); // ०
220
+ let isDevanagari = true;
221
+ let num = 0;
222
+ for (const char of value) {
223
+ const code = char.charCodeAt(0);
224
+ if (code >= devanagariZero && code <= devanagariZero + 9) {
225
+ num = num * 10 + (code - devanagariZero);
226
+ } else if (char >= '0' && char <= '9') {
227
+ isDevanagari = false;
228
+ } else {
229
+ throw new Error(`Cannot convert '${value}' to number`);
230
+ }
231
+ }
232
+ if (isDevanagari && value.length > 0) return num;
233
+ // Otherwise try regular parsing
234
+ const parsed = parseFloat(value);
235
+ if (isNaN(parsed)) {
236
+ throw new Error(`Cannot convert '${value}' to number`);
237
+ }
238
+ return parsed;
239
+ }
240
+ throw new Error(`Cannot convert '${value}' to number`);
241
+ }
242
+
109
243
  /*
110
244
  * Evaluate an expression
111
245
  */
package/src/main.ts CHANGED
@@ -36,3 +36,6 @@ function main(): void {
36
36
  }
37
37
 
38
38
  main();
39
+
40
+ // Export Lexer, Parser, and Interpreter
41
+ export { Lexer, Parser, Interpreter };
package/src/parser.ts CHANGED
@@ -1,9 +1,11 @@
1
1
  import {
2
+ BinaryExpr,
2
3
  CallExpr,
3
4
  ExpressionStmt,
4
5
  FunctionStmt,
5
6
  LiteralExpr,
6
7
  PrintStmt,
8
+ ReturnStmt,
7
9
  VarStmt,
8
10
  VariableExpr,
9
11
  type Expr,
@@ -46,9 +48,22 @@ export class Parser {
46
48
  return this.parseFunctionStmt();
47
49
  }
48
50
 
51
+ if (this.match(TokenKind.Return)) {
52
+ return this.parseReturnStmt();
53
+ }
54
+
49
55
  return this.parseExpressionStmt();
50
56
  }
51
57
 
58
+ private parseReturnStmt(): ReturnStmt {
59
+ let value: Expr | null = null;
60
+ // Check if there's a value to return (not just "फिर्ता" alone)
61
+ if (!this.check(TokenKind.CloseCurly) && !this.isAtEnd()) {
62
+ value = this.parseExpression();
63
+ }
64
+ return new ReturnStmt(value);
65
+ }
66
+
52
67
  private parseExpressionStmt(): ExpressionStmt {
53
68
  const expr = this.parseExpression();
54
69
  return new ExpressionStmt(expr);
@@ -111,7 +126,31 @@ export class Parser {
111
126
  }
112
127
 
113
128
  private parseExpression(): Expr {
114
- return this.parseCall();
129
+ return this.parseAddition();
130
+ }
131
+
132
+ private parseAddition(): Expr {
133
+ let expr = this.parseMultiplication();
134
+
135
+ while (this.match(TokenKind.Plus, TokenKind.Minus)) {
136
+ const operator = this.previous();
137
+ const right = this.parseMultiplication();
138
+ expr = new BinaryExpr(expr, operator, right);
139
+ }
140
+
141
+ return expr;
142
+ }
143
+
144
+ private parseMultiplication(): Expr {
145
+ let expr = this.parseCall();
146
+
147
+ while (this.match(TokenKind.Star, TokenKind.Slash, TokenKind.Mod)) {
148
+ const operator = this.previous();
149
+ const right = this.parseCall();
150
+ expr = new BinaryExpr(expr, operator, right);
151
+ }
152
+
153
+ return expr;
115
154
  }
116
155
 
117
156
  private parseCall(): Expr {
@@ -145,6 +184,12 @@ export class Parser {
145
184
  return new VariableExpr(this.previous());
146
185
  }
147
186
 
187
+ if (this.match(TokenKind.OpenParen)) {
188
+ const expr = this.parseExpression();
189
+ this.consume(TokenKind.CloseParen, "Expect ')' after expression");
190
+ return expr;
191
+ }
192
+
148
193
  throw this.error(this.peek(), 'Expression expected.');
149
194
  }
150
195
 
@@ -0,0 +1,176 @@
1
+ import test, { describe } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { Lexer } from '../src/lexer.js';
4
+ import { Parser } from '../src/parser.js';
5
+ import { Interpreter } from '../src/interpreter.js';
6
+
7
+ describe('Interpreter', () => {
8
+ function runSource(source: string): string[] {
9
+ const lexer = new Lexer(source.trim());
10
+ const tokens = lexer.readTokens();
11
+ const parser = new Parser(tokens);
12
+ const stmts = parser.parse();
13
+ const interpreter = new Interpreter();
14
+
15
+ const outputs: string[] = [];
16
+ const originalLog = console.log;
17
+ console.log = (...args: any[]) => {
18
+ outputs.push(args.join(' '));
19
+ };
20
+
21
+ try {
22
+ interpreter.interpret(stmts);
23
+ } finally {
24
+ console.log = originalLog;
25
+ }
26
+
27
+ return outputs;
28
+ }
29
+
30
+ test('evaluate addition with Devanagari numbers', () => {
31
+ const source = `मानौ क = ५ + ३
32
+ छाप(क)`;
33
+ const outputs = runSource(source);
34
+ assert.equal(outputs.length, 1);
35
+ assert.equal(outputs[0], '८');
36
+ });
37
+
38
+ test('evaluate subtraction', () => {
39
+ const source = `मानौ क = १० - २
40
+ छाप(क)`;
41
+ const outputs = runSource(source);
42
+ assert.equal(outputs.length, 1);
43
+ assert.equal(outputs[0], '८');
44
+ });
45
+
46
+ test('evaluate multiplication', () => {
47
+ const source = `मानौ क = ४ * २
48
+ छाप(क)`;
49
+ const outputs = runSource(source);
50
+ assert.equal(outputs.length, 1);
51
+ assert.equal(outputs[0], '८');
52
+ });
53
+
54
+ test('evaluate division', () => {
55
+ const source = `मानौ क = १५ / ३
56
+ छाप(क)`;
57
+ const outputs = runSource(source);
58
+ assert.equal(outputs.length, 1);
59
+ assert.equal(outputs[0], '५');
60
+ });
61
+
62
+ test('evaluate modulo', () => {
63
+ const source = `मानौ क = १७ % ५
64
+ छाप(क)`;
65
+ const outputs = runSource(source);
66
+ assert.equal(outputs.length, 1);
67
+ assert.equal(outputs[0], '२');
68
+ });
69
+
70
+ test('evaluate operator precedence', () => {
71
+ const source = `मानौ क = २ + ३ * ४
72
+ छाप(क)`;
73
+ const outputs = runSource(source);
74
+ assert.equal(outputs.length, 1);
75
+ assert.equal(outputs[0], '१४');
76
+ });
77
+
78
+ test('evaluate parentheses grouping', () => {
79
+ const source = `मानौ क = (२ + ३) * ४
80
+ छाप(क)`;
81
+ const outputs = runSource(source);
82
+ assert.equal(outputs.length, 1);
83
+ assert.equal(outputs[0], '२०');
84
+ });
85
+
86
+ test('evaluate chained addition', () => {
87
+ const source = `मानौ क = १ + २ + ३
88
+ छाप(क)`;
89
+ const outputs = runSource(source);
90
+ assert.equal(outputs.length, 1);
91
+ assert.equal(outputs[0], '६');
92
+ });
93
+
94
+ test('evaluate mixed operations', () => {
95
+ const source = `मानौ क = १० - ३ * २ + ४
96
+ छाप(क)`;
97
+ const outputs = runSource(source);
98
+ assert.equal(outputs.length, 1);
99
+ assert.equal(outputs[0], '८');
100
+ });
101
+
102
+ test('evaluate division by zero throws error', () => {
103
+ const source = `मानौ क = १० / ०
104
+ छाप(क)`;
105
+ assert.throws(() => {
106
+ runSource(source);
107
+ }, /Division by zero/);
108
+ });
109
+
110
+ test('evaluate modulo by zero throws error', () => {
111
+ const source = `मानौ क = १० % ०
112
+ छाप(क)`;
113
+ assert.throws(() => {
114
+ runSource(source);
115
+ }, /Modulo by zero/);
116
+ });
117
+
118
+ test('evaluate complex expression', () => {
119
+ const source = `मानौ क = (५ + ५) * (३ - १)
120
+ छाप(क)`;
121
+ const outputs = runSource(source);
122
+ assert.equal(outputs.length, 1);
123
+ assert.equal(outputs[0], '२०');
124
+ });
125
+
126
+ test('evaluate multiple print statements', () => {
127
+ const source = `मानौ क = ५ + ५
128
+ छाप(क)
129
+ मानौ ख = क + ५
130
+ छाप(ख)`;
131
+ const outputs = runSource(source);
132
+ assert.equal(outputs.length, 2);
133
+ assert.equal(outputs[0], '१०');
134
+ assert.equal(outputs[1], '१५');
135
+ });
136
+
137
+ test('print string literal', () => {
138
+ const source = `छाप("नमस्ते")`;
139
+ const outputs = runSource(source);
140
+ assert.equal(outputs.length, 1);
141
+ assert.equal(outputs[0], 'नमस्ते');
142
+ });
143
+
144
+ test('function returns value', () => {
145
+ const source = `कार्य जोड(क, ख) {
146
+ फिर्ता क + ख
147
+ }
148
+ मानौ परिणाम = जोड(२, ३)
149
+ छाप(परिणाम)`;
150
+ const outputs = runSource(source);
151
+ assert.equal(outputs.length, 1);
152
+ assert.equal(outputs[0], '५');
153
+ });
154
+
155
+ test('function returns expression result', () => {
156
+ const source = `कार्य गुणा(क, ख) {
157
+ फिर्ता क * ख + १०
158
+ }
159
+ मानौ परिणाम = गुणा(३, ४)
160
+ छाप(परिणाम)`;
161
+ const outputs = runSource(source);
162
+ assert.equal(outputs.length, 1);
163
+ assert.equal(outputs[0], '२२');
164
+ });
165
+
166
+ test('function returns string', () => {
167
+ const source = `कार्य नमस्कार() {
168
+ फिर्ता "नमस्ते संसार"
169
+ }
170
+ मानौ सन्देश = नमस्कार()
171
+ छाप(सन्देश)`;
172
+ const outputs = runSource(source);
173
+ assert.equal(outputs.length, 1);
174
+ assert.equal(outputs[0], 'नमस्ते संसार');
175
+ });
176
+ });
@@ -7,7 +7,8 @@ import {
7
7
  VarStmt,
8
8
  FunctionStmt,
9
9
  LiteralExpr,
10
- VariableExpr
10
+ VariableExpr,
11
+ BinaryExpr
11
12
  } from '../src/ast.js';
12
13
 
13
14
  describe('Parser', () => {
@@ -128,4 +129,91 @@ describe('Parser', () => {
128
129
  assert.equal(funcStmt.params[1]!.lexeme, 'ब');
129
130
  assert.equal(funcStmt.body.length, 2);
130
131
  });
132
+
133
+ test('parse binary expression with addition', () => {
134
+ const source: string = `मानौ क = ५ + ३`;
135
+ const lexer: Lexer = new Lexer(source.trim());
136
+ const tokens = lexer.readTokens();
137
+ const parser: Parser = new Parser(tokens);
138
+ const stmts = parser.parse();
139
+
140
+ assert.equal(stmts.length, 1);
141
+ assert.ok(stmts[0] instanceof VarStmt);
142
+ const varStmt = stmts[0] as VarStmt;
143
+ assert.ok(varStmt.initializer instanceof BinaryExpr);
144
+ const binaryExpr = varStmt.initializer as BinaryExpr;
145
+ assert.ok(binaryExpr.left instanceof LiteralExpr);
146
+ assert.ok(binaryExpr.right instanceof LiteralExpr);
147
+ });
148
+
149
+ test('parse binary expression with operator precedence', () => {
150
+ const source: string = `मानौ क = २ + ३ * ४`;
151
+ const lexer: Lexer = new Lexer(source.trim());
152
+ const tokens = lexer.readTokens();
153
+ const parser: Parser = new Parser(tokens);
154
+ const stmts = parser.parse();
155
+
156
+ assert.equal(stmts.length, 1);
157
+ assert.ok(stmts[0] instanceof VarStmt);
158
+ const varStmt = stmts[0] as VarStmt;
159
+ assert.ok(varStmt.initializer instanceof BinaryExpr);
160
+ const binaryExpr = varStmt.initializer as BinaryExpr;
161
+ assert.ok(binaryExpr.left instanceof LiteralExpr);
162
+ assert.ok(binaryExpr.right instanceof BinaryExpr);
163
+ });
164
+
165
+ test('parse binary expression with parentheses', () => {
166
+ const source: string = `मानौ क = (२ + ३) * ४`;
167
+ const lexer: Lexer = new Lexer(source.trim());
168
+ const tokens = lexer.readTokens();
169
+ const parser: Parser = new Parser(tokens);
170
+ const stmts = parser.parse();
171
+
172
+ assert.equal(stmts.length, 1);
173
+ assert.ok(stmts[0] instanceof VarStmt);
174
+ const varStmt = stmts[0] as VarStmt;
175
+ assert.ok(varStmt.initializer instanceof BinaryExpr);
176
+ const binaryExpr = varStmt.initializer as BinaryExpr;
177
+ assert.ok(binaryExpr.left instanceof BinaryExpr);
178
+ assert.ok(binaryExpr.right instanceof LiteralExpr);
179
+ });
180
+
181
+ test('parse binary expression with subtraction', () => {
182
+ const source: string = `मानौ क = १० - ५`;
183
+ const lexer: Lexer = new Lexer(source.trim());
184
+ const tokens = lexer.readTokens();
185
+ const parser: Parser = new Parser(tokens);
186
+ const stmts = parser.parse();
187
+
188
+ assert.equal(stmts.length, 1);
189
+ assert.ok(stmts[0] instanceof VarStmt);
190
+ const varStmt = stmts[0] as VarStmt;
191
+ assert.ok(varStmt.initializer instanceof BinaryExpr);
192
+ });
193
+
194
+ test('parse binary expression with division', () => {
195
+ const source: string = `मानौ क = १५ / ३`;
196
+ const lexer: Lexer = new Lexer(source.trim());
197
+ const tokens = lexer.readTokens();
198
+ const parser: Parser = new Parser(tokens);
199
+ const stmts = parser.parse();
200
+
201
+ assert.equal(stmts.length, 1);
202
+ assert.ok(stmts[0] instanceof VarStmt);
203
+ const varStmt = stmts[0] as VarStmt;
204
+ assert.ok(varStmt.initializer instanceof BinaryExpr);
205
+ });
206
+
207
+ test('parse binary expression with modulo', () => {
208
+ const source: string = `मानौ क = १७ % ५`;
209
+ const lexer: Lexer = new Lexer(source.trim());
210
+ const tokens = lexer.readTokens();
211
+ const parser: Parser = new Parser(tokens);
212
+ const stmts = parser.parse();
213
+
214
+ assert.equal(stmts.length, 1);
215
+ assert.ok(stmts[0] instanceof VarStmt);
216
+ const varStmt = stmts[0] as VarStmt;
217
+ assert.ok(varStmt.initializer instanceof BinaryExpr);
218
+ });
131
219
  });
package/tsconfig.json CHANGED
@@ -9,6 +9,7 @@
9
9
  "moduleDetection": "force",
10
10
  "isolatedModules": true,
11
11
  "verbatimModuleSyntax": true,
12
+ "declaration": true,
12
13
 
13
14
  /* Strictness */
14
15
  "strict": true,
@@ -20,6 +21,6 @@
20
21
  "rootDir": ".",
21
22
  "outDir": "./dist"
22
23
  },
23
- "include": ["src/**/*.ts", "tests/**/*.test.ts"],
24
+ "include": ["src/**/*.ts"],
24
25
  "exclude": ["node_modules", "dist"]
25
26
  }
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "declaration": false,
5
+ "noEmit": false,
6
+ "outDir": "./dist"
7
+ },
8
+ "include": ["tests/**/*.ts", "src/**/*.ts"]
9
+ }