ga-lang 1.2.0 → 1.2.3

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/dist/src/ast.js CHANGED
@@ -16,6 +16,30 @@ export class LiteralExpr {
16
16
  return visitor.visitLiteralExpr(this);
17
17
  }
18
18
  }
19
+ export class CallExpr {
20
+ callee;
21
+ args;
22
+ constructor(callee, args) {
23
+ this.callee = callee;
24
+ this.args = args;
25
+ }
26
+ accept(visitor) {
27
+ return visitor.visitCallExpr(this);
28
+ }
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
+ }
19
43
  export class PrintStmt {
20
44
  expression;
21
45
  constructor(expression) {
@@ -36,3 +60,43 @@ export class VarStmt {
36
60
  return visitor.visitVarStmt(this);
37
61
  }
38
62
  }
63
+ export class FunctionStmt {
64
+ name;
65
+ params;
66
+ body;
67
+ constructor(name, params, body) {
68
+ this.name = name;
69
+ this.params = params;
70
+ this.body = body;
71
+ }
72
+ accept(visitor) {
73
+ return visitor.visitFunctionStmt(this);
74
+ }
75
+ }
76
+ export class BlockStmt {
77
+ statements;
78
+ constructor(statements) {
79
+ this.statements = statements;
80
+ }
81
+ accept(visitor) {
82
+ return visitor.visitBlockStmt(this);
83
+ }
84
+ }
85
+ export class ExpressionStmt {
86
+ expression;
87
+ constructor(expression) {
88
+ this.expression = expression;
89
+ }
90
+ accept(visitor) {
91
+ return visitor.visitExpressionStmt(this);
92
+ }
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
+ }
@@ -1,3 +1,12 @@
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
+ }
1
10
  export class Interpreter {
2
11
  environment = new Map();
3
12
  interpret(statements) {
@@ -13,18 +22,76 @@ export class Interpreter {
13
22
  */
14
23
  visitPrintStmt(stmt) {
15
24
  const value = this.evaluate(stmt.expression);
16
- 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('');
17
45
  }
18
46
  visitVarStmt(stmt) {
19
47
  const value = stmt.initializer !== null ? this.evaluate(stmt.initializer) : null;
20
48
  this.environment.set(stmt.name.lexeme, value);
21
49
  }
50
+ visitFunctionStmt(stmt) {
51
+ this.environment.set(stmt.name.lexeme, stmt);
52
+ }
53
+ visitBlockStmt(stmt) {
54
+ for (const statement of stmt.statements) {
55
+ this.execute(statement);
56
+ }
57
+ }
58
+ visitExpressionStmt(stmt) {
59
+ this.evaluate(stmt.expression);
60
+ }
61
+ visitReturnStmt(stmt) {
62
+ const value = stmt.value !== null ? this.evaluate(stmt.value) : null;
63
+ throw new ReturnValue(value);
64
+ }
22
65
  visitLiteralExpr(expr) {
23
66
  if (typeof expr.value === 'string' && expr.value.startsWith('"')) {
24
67
  return expr.value.slice(1, -1);
25
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
+ }
26
73
  return expr.value;
27
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
+ }
28
95
  visitVariableExpr(expr) {
29
96
  const value = this.environment.get(expr.name.lexeme);
30
97
  if (value === undefined) {
@@ -32,6 +99,105 @@ export class Interpreter {
32
99
  }
33
100
  return value;
34
101
  }
102
+ visitCallExpr(expr) {
103
+ const func = this.environment.get(expr.callee.name.lexeme);
104
+ if (func === undefined) {
105
+ throw new Error(`Undefined function '${expr.callee.name.lexeme}'`);
106
+ }
107
+ if (!(func instanceof FunctionStmt)) {
108
+ throw new Error(`'${expr.callee.name.lexeme}' is not a function`);
109
+ }
110
+ // Create new environment for function scope
111
+ const prevEnvironment = this.environment;
112
+ this.environment = new Map(this.environment);
113
+ // Bind arguments to parameters
114
+ if (expr.args.length !== func.params.length) {
115
+ throw new Error(`Expected ${func.params.length} arguments but got ${expr.args.length}`);
116
+ }
117
+ for (let i = 0; i < func.params.length; i++) {
118
+ const value = this.evaluate(expr.args[i]);
119
+ this.environment.set(func.params[i].lexeme, value);
120
+ }
121
+ // Execute function body
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;
134
+ }
135
+ // Restore previous environment
136
+ this.environment = prevEnvironment;
137
+ return null;
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
+ }
35
201
  /*
36
202
  * Evaluate an expression
37
203
  */
package/dist/src/lexer.js CHANGED
@@ -92,6 +92,11 @@ export class Lexer {
92
92
  else
93
93
  this.readDevanagariIdentifier();
94
94
  }
95
+ else if (!this.isWhitespace(char)) {
96
+ // Report invalid character
97
+ throw new Error(`Invalid character '${char}' at line ${this.line}. ` +
98
+ `Only Devanagari characters, numbers, special characters, and whitespace are allowed.`);
99
+ }
95
100
  break;
96
101
  }
97
102
  }
@@ -149,6 +154,13 @@ export class Lexer {
149
154
  isDevanagariDigit(char) {
150
155
  return '\u{0966}' <= char && char <= '\u{096F}';
151
156
  }
157
+ /**
158
+ * Check if the character is whitespace
159
+ * @param {string} char
160
+ */
161
+ isWhitespace(char) {
162
+ return char === ' ' || char === '\r' || char === '\t' || char === '\n';
163
+ }
152
164
  /**
153
165
  * Read devanagari character sequence as number
154
166
  */
@@ -1,4 +1,4 @@
1
- import { 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;
@@ -10,6 +10,10 @@ export class Parser {
10
10
  parse() {
11
11
  const stmts = [];
12
12
  while (!this.isAtEnd()) {
13
+ if (this.check(TokenKind.Illegal)) {
14
+ const token = this.peek();
15
+ throw this.error(token, `Invalid character '${token.lexeme}'`);
16
+ }
13
17
  stmts.push(this.parseStmt());
14
18
  }
15
19
  return stmts;
@@ -21,7 +25,25 @@ export class Parser {
21
25
  if (this.match(TokenKind.Let)) {
22
26
  return this.parseVarStmt();
23
27
  }
24
- throw this.error(this.peek(), 'Expression expected.');
28
+ if (this.match(TokenKind.Function)) {
29
+ return this.parseFunctionStmt();
30
+ }
31
+ if (this.match(TokenKind.Return)) {
32
+ return this.parseReturnStmt();
33
+ }
34
+ return this.parseExpressionStmt();
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
+ }
44
+ parseExpressionStmt() {
45
+ const expr = this.parseExpression();
46
+ return new ExpressionStmt(expr);
25
47
  }
26
48
  parsePrintStmt() {
27
49
  this.consume(TokenKind.OpenParen, "Expect '(' after 'छाप'");
@@ -37,8 +59,67 @@ export class Parser {
37
59
  }
38
60
  return new VarStmt(name, initializer);
39
61
  }
62
+ parseFunctionStmt() {
63
+ const name = this.consume(TokenKind.Identifier, "Expect function name after 'कार्य'");
64
+ this.consume(TokenKind.OpenParen, "Expect '(' after function name");
65
+ const params = [];
66
+ if (!this.check(TokenKind.CloseParen)) {
67
+ do {
68
+ params.push(this.consume(TokenKind.Identifier, 'Expect parameter name'));
69
+ } while (this.match(TokenKind.Comma));
70
+ }
71
+ this.consume(TokenKind.CloseParen, "Expect ')' after parameters");
72
+ this.consume(TokenKind.OpenCurly, "Expect '{' before function body");
73
+ const body = this.parseBlock();
74
+ return new FunctionStmt(name, params, body);
75
+ }
76
+ parseBlock() {
77
+ const statements = [];
78
+ while (!this.check(TokenKind.CloseCurly) && !this.isAtEnd()) {
79
+ statements.push(this.parseStmt());
80
+ }
81
+ this.consume(TokenKind.CloseCurly, "Expect '}' after block");
82
+ return statements;
83
+ }
40
84
  parseExpression() {
41
- return this.parsePrimary();
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;
104
+ }
105
+ parseCall() {
106
+ let expr = this.parsePrimary();
107
+ while (this.match(TokenKind.OpenParen)) {
108
+ const args = [];
109
+ if (!this.check(TokenKind.CloseParen)) {
110
+ do {
111
+ args.push(this.parseExpression());
112
+ } while (this.match(TokenKind.Comma));
113
+ }
114
+ this.consume(TokenKind.CloseParen, "Expect ')' after arguments");
115
+ if (expr instanceof VariableExpr) {
116
+ expr = new CallExpr(expr, args);
117
+ }
118
+ else {
119
+ throw this.error(this.previous(), 'Can only call functions');
120
+ }
121
+ }
122
+ return expr;
42
123
  }
43
124
  parsePrimary() {
44
125
  if (this.match(TokenKind.String, TokenKind.Number)) {
@@ -47,6 +128,11 @@ export class Parser {
47
128
  if (this.match(TokenKind.Identifier)) {
48
129
  return new VariableExpr(this.previous());
49
130
  }
131
+ if (this.match(TokenKind.OpenParen)) {
132
+ const expr = this.parseExpression();
133
+ this.consume(TokenKind.CloseParen, "Expect ')' after expression");
134
+ return expr;
135
+ }
50
136
  throw this.error(this.peek(), 'Expression expected.');
51
137
  }
52
138
  match(...kinds) {
package/examples/init.ga CHANGED
@@ -1,4 +1,6 @@
1
- मानौ सन्देश = "सोच्छौ के मेरो बारे?"
2
- मानौ एकदुइतिन = १२३
3
- छाप(सन्देश)
4
- छाप(एकदुइतिन)
1
+ कार्य जोड (अ , आ) {
2
+ फिर्ता +
3
+ }
4
+
5
+ मानौ जम्मा = जोड(१, २)
6
+ छाप(जम्मा)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ga-lang",
3
- "version": "1.2.0",
3
+ "version": "1.2.3",
4
4
  "description": "An interpreted toy programming language",
5
5
  "type": "module",
6
6
  "main": "dist/main.js",
package/src/ast.ts CHANGED
@@ -7,6 +7,8 @@ export interface Expr {
7
7
  export interface ExprVisitor<T> {
8
8
  visitVariableExpr(expr: VariableExpr): T;
9
9
  visitLiteralExpr(expr: LiteralExpr): T;
10
+ visitCallExpr(expr: CallExpr): T;
11
+ visitBinaryExpr(expr: BinaryExpr): T;
10
12
  }
11
13
 
12
14
  export interface Stmt {
@@ -16,6 +18,10 @@ export interface Stmt {
16
18
  export interface StmtVisitor<T> {
17
19
  visitPrintStmt(stmt: PrintStmt): T;
18
20
  visitVarStmt(stmt: VarStmt): T;
21
+ visitFunctionStmt(stmt: FunctionStmt): T;
22
+ visitBlockStmt(stmt: BlockStmt): T;
23
+ visitExpressionStmt(stmt: ExpressionStmt): T;
24
+ visitReturnStmt(stmt: ReturnStmt): T;
19
25
  }
20
26
 
21
27
  export class VariableExpr implements Expr {
@@ -42,6 +48,36 @@ export class LiteralExpr implements Expr {
42
48
  }
43
49
  }
44
50
 
51
+ export class CallExpr implements Expr {
52
+ callee: VariableExpr;
53
+ args: Expr[];
54
+
55
+ constructor(callee: VariableExpr, args: Expr[]) {
56
+ this.callee = callee;
57
+ this.args = args;
58
+ }
59
+
60
+ accept<T>(visitor: ExprVisitor<T>): T {
61
+ return visitor.visitCallExpr(this);
62
+ }
63
+ }
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
+
45
81
  export class PrintStmt implements Stmt {
46
82
  expression: Expr;
47
83
 
@@ -67,3 +103,55 @@ export class VarStmt implements Stmt {
67
103
  return visitor.visitVarStmt(this);
68
104
  }
69
105
  }
106
+
107
+ export class FunctionStmt implements Stmt {
108
+ name: Token;
109
+ params: Token[];
110
+ body: Stmt[];
111
+
112
+ constructor(name: Token, params: Token[], body: Stmt[]) {
113
+ this.name = name;
114
+ this.params = params;
115
+ this.body = body;
116
+ }
117
+
118
+ accept<T>(visitor: StmtVisitor<T>): T {
119
+ return visitor.visitFunctionStmt(this);
120
+ }
121
+ }
122
+
123
+ export class BlockStmt implements Stmt {
124
+ statements: Stmt[];
125
+
126
+ constructor(statements: Stmt[]) {
127
+ this.statements = statements;
128
+ }
129
+
130
+ accept<T>(visitor: StmtVisitor<T>): T {
131
+ return visitor.visitBlockStmt(this);
132
+ }
133
+ }
134
+
135
+ export class ExpressionStmt implements Stmt {
136
+ expression: Expr;
137
+
138
+ constructor(expression: Expr) {
139
+ this.expression = expression;
140
+ }
141
+
142
+ accept<T>(visitor: StmtVisitor<T>): T {
143
+ return visitor.visitExpressionStmt(this);
144
+ }
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,13 +1,28 @@
1
- import type {
2
- Expr,
3
- ExprVisitor,
4
- LiteralExpr,
5
- PrintStmt,
6
- Stmt,
7
- StmtVisitor,
8
- VariableExpr,
9
- VarStmt
1
+ import {
2
+ BinaryExpr,
3
+ FunctionStmt,
4
+ ReturnStmt,
5
+ type BlockStmt,
6
+ type CallExpr,
7
+ type ExpressionStmt,
8
+ type Expr,
9
+ type ExprVisitor,
10
+ type LiteralExpr,
11
+ type PrintStmt,
12
+ type Stmt,
13
+ type StmtVisitor,
14
+ type VariableExpr,
15
+ type VarStmt
10
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
+ }
11
26
 
12
27
  export class Interpreter implements ExprVisitor<any>, StmtVisitor<void> {
13
28
  private environment: Map<string, any> = new Map();
@@ -27,7 +42,28 @@ export class Interpreter implements ExprVisitor<any>, StmtVisitor<void> {
27
42
  */
28
43
  visitPrintStmt(stmt: PrintStmt): void {
29
44
  const value = this.evaluate(stmt.expression);
30
- 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('');
31
67
  }
32
68
 
33
69
  visitVarStmt(stmt: VarStmt): void {
@@ -36,14 +72,59 @@ export class Interpreter implements ExprVisitor<any>, StmtVisitor<void> {
36
72
  this.environment.set(stmt.name.lexeme, value);
37
73
  }
38
74
 
75
+ visitFunctionStmt(stmt: FunctionStmt): void {
76
+ this.environment.set(stmt.name.lexeme, stmt);
77
+ }
78
+
79
+ visitBlockStmt(stmt: BlockStmt): void {
80
+ for (const statement of stmt.statements) {
81
+ this.execute(statement);
82
+ }
83
+ }
84
+
85
+ visitExpressionStmt(stmt: ExpressionStmt): void {
86
+ this.evaluate(stmt.expression);
87
+ }
88
+
89
+ visitReturnStmt(stmt: ReturnStmt): void {
90
+ const value = stmt.value !== null ? this.evaluate(stmt.value) : null;
91
+ throw new ReturnValue(value);
92
+ }
93
+
39
94
  visitLiteralExpr(expr: LiteralExpr): any {
40
95
  if (typeof expr.value === 'string' && expr.value.startsWith('"')) {
41
96
  return expr.value.slice(1, -1);
42
97
  }
43
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
+
44
104
  return expr.value;
45
105
  }
46
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
+
47
128
  visitVariableExpr(expr: VariableExpr): any {
48
129
  const value = this.environment.get(expr.name.lexeme);
49
130
  if (value === undefined) {
@@ -52,6 +133,113 @@ export class Interpreter implements ExprVisitor<any>, StmtVisitor<void> {
52
133
  return value;
53
134
  }
54
135
 
136
+ visitCallExpr(expr: CallExpr): any {
137
+ const func = this.environment.get(expr.callee.name.lexeme);
138
+ if (func === undefined) {
139
+ throw new Error(`Undefined function '${expr.callee.name.lexeme}'`);
140
+ }
141
+ if (!(func instanceof FunctionStmt)) {
142
+ throw new Error(`'${expr.callee.name.lexeme}' is not a function`);
143
+ }
144
+
145
+ // Create new environment for function scope
146
+ const prevEnvironment = this.environment;
147
+ this.environment = new Map(this.environment);
148
+
149
+ // Bind arguments to parameters
150
+ if (expr.args.length !== func.params.length) {
151
+ throw new Error(
152
+ `Expected ${func.params.length} arguments but got ${expr.args.length}`
153
+ );
154
+ }
155
+
156
+ for (let i = 0; i < func.params.length; i++) {
157
+ const value = this.evaluate(expr.args[i]!);
158
+ this.environment.set(func.params[i]!.lexeme, value);
159
+ }
160
+
161
+ // Execute function body
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;
173
+ }
174
+
175
+ // Restore previous environment
176
+ this.environment = prevEnvironment;
177
+
178
+ return null;
179
+ }
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
+
55
243
  /*
56
244
  * Evaluate an expression
57
245
  */
package/src/lexer.ts CHANGED
@@ -96,6 +96,12 @@ export class Lexer {
96
96
  if (this.isDevnagariChar(char)) {
97
97
  if (this.isDevanagariDigit(char)) this.readDevanagariDigit();
98
98
  else this.readDevanagariIdentifier();
99
+ } else if (!this.isWhitespace(char)) {
100
+ // Report invalid character
101
+ throw new Error(
102
+ `Invalid character '${char}' at line ${this.line}. ` +
103
+ `Only Devanagari characters, numbers, special characters, and whitespace are allowed.`
104
+ );
99
105
  }
100
106
  break;
101
107
  }
@@ -161,6 +167,14 @@ export class Lexer {
161
167
  return '\u{0966}' <= char && char <= '\u{096F}';
162
168
  }
163
169
 
170
+ /**
171
+ * Check if the character is whitespace
172
+ * @param {string} char
173
+ */
174
+ private isWhitespace(char: string): boolean {
175
+ return char === ' ' || char === '\r' || char === '\t' || char === '\n';
176
+ }
177
+
164
178
  /**
165
179
  * Read devanagari character sequence as number
166
180
  */
package/src/parser.ts CHANGED
@@ -1,6 +1,11 @@
1
1
  import {
2
+ BinaryExpr,
3
+ CallExpr,
4
+ ExpressionStmt,
5
+ FunctionStmt,
2
6
  LiteralExpr,
3
7
  PrintStmt,
8
+ ReturnStmt,
4
9
  VarStmt,
5
10
  VariableExpr,
6
11
  type Expr,
@@ -20,6 +25,10 @@ export class Parser {
20
25
  parse(): Stmt[] {
21
26
  const stmts: Stmt[] = [];
22
27
  while (!this.isAtEnd()) {
28
+ if (this.check(TokenKind.Illegal)) {
29
+ const token = this.peek();
30
+ throw this.error(token, `Invalid character '${token.lexeme}'`);
31
+ }
23
32
  stmts.push(this.parseStmt());
24
33
  }
25
34
 
@@ -35,7 +44,29 @@ export class Parser {
35
44
  return this.parseVarStmt();
36
45
  }
37
46
 
38
- throw this.error(this.peek(), 'Expression expected.');
47
+ if (this.match(TokenKind.Function)) {
48
+ return this.parseFunctionStmt();
49
+ }
50
+
51
+ if (this.match(TokenKind.Return)) {
52
+ return this.parseReturnStmt();
53
+ }
54
+
55
+ return this.parseExpressionStmt();
56
+ }
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
+
67
+ private parseExpressionStmt(): ExpressionStmt {
68
+ const expr = this.parseExpression();
69
+ return new ExpressionStmt(expr);
39
70
  }
40
71
 
41
72
  private parsePrintStmt(): Stmt {
@@ -59,8 +90,89 @@ export class Parser {
59
90
  return new VarStmt(name, initializer);
60
91
  }
61
92
 
93
+ private parseFunctionStmt(): FunctionStmt {
94
+ const name = this.consume(
95
+ TokenKind.Identifier,
96
+ "Expect function name after 'कार्य'"
97
+ );
98
+ this.consume(TokenKind.OpenParen, "Expect '(' after function name");
99
+
100
+ const params: Token[] = [];
101
+ if (!this.check(TokenKind.CloseParen)) {
102
+ do {
103
+ params.push(
104
+ this.consume(TokenKind.Identifier, 'Expect parameter name')
105
+ );
106
+ } while (this.match(TokenKind.Comma));
107
+ }
108
+
109
+ this.consume(TokenKind.CloseParen, "Expect ')' after parameters");
110
+ this.consume(TokenKind.OpenCurly, "Expect '{' before function body");
111
+
112
+ const body = this.parseBlock();
113
+
114
+ return new FunctionStmt(name, params, body);
115
+ }
116
+
117
+ private parseBlock(): Stmt[] {
118
+ const statements: Stmt[] = [];
119
+
120
+ while (!this.check(TokenKind.CloseCurly) && !this.isAtEnd()) {
121
+ statements.push(this.parseStmt());
122
+ }
123
+
124
+ this.consume(TokenKind.CloseCurly, "Expect '}' after block");
125
+ return statements;
126
+ }
127
+
62
128
  private parseExpression(): Expr {
63
- return this.parsePrimary();
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;
154
+ }
155
+
156
+ private parseCall(): Expr {
157
+ let expr = this.parsePrimary();
158
+
159
+ while (this.match(TokenKind.OpenParen)) {
160
+ const args: Expr[] = [];
161
+ if (!this.check(TokenKind.CloseParen)) {
162
+ do {
163
+ args.push(this.parseExpression());
164
+ } while (this.match(TokenKind.Comma));
165
+ }
166
+ this.consume(TokenKind.CloseParen, "Expect ')' after arguments");
167
+
168
+ if (expr instanceof VariableExpr) {
169
+ expr = new CallExpr(expr, args);
170
+ } else {
171
+ throw this.error(this.previous(), 'Can only call functions');
172
+ }
173
+ }
174
+
175
+ return expr;
64
176
  }
65
177
 
66
178
  private parsePrimary(): Expr {
@@ -72,6 +184,12 @@ export class Parser {
72
184
  return new VariableExpr(this.previous());
73
185
  }
74
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
+
75
193
  throw this.error(this.peek(), 'Expression expected.');
76
194
  }
77
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
+ });
@@ -85,4 +85,32 @@ describe('Lexer', () => {
85
85
  assert.equal(actual.length, expected.length);
86
86
  assert.deepEqual(actual, expected);
87
87
  });
88
+
89
+ test('reject non-devanagari characters outside string literals', () => {
90
+ const source = `मानौ x = ५`;
91
+
92
+ const lexer = new Lexer(source);
93
+
94
+ assert.throws(() => {
95
+ lexer.readTokens();
96
+ }, /Invalid character 'x' at line 1/);
97
+ });
98
+
99
+ test('allow non-devanagari characters inside string literals', () => {
100
+ const source = `मानौ सन्देश = "Hello World. 123!"`;
101
+
102
+ const lexer = new Lexer(source);
103
+ const actual = lexer.readTokens();
104
+
105
+ const expected = [
106
+ new Token(TokenKind.Let, 'मानौ', 1),
107
+ new Token(TokenKind.Identifier, 'सन्देश', 1),
108
+ new Token(TokenKind.Equal, '=', 1),
109
+ new Token(TokenKind.String, '"Hello World. 123!"', 1),
110
+ new Token(TokenKind.Eof, '', 1)
111
+ ];
112
+
113
+ assert.equal(actual.length, expected.length);
114
+ assert.deepEqual(actual, expected);
115
+ });
88
116
  });
@@ -2,7 +2,14 @@ import test, { describe } from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
3
  import { Lexer } from '../src/lexer.js';
4
4
  import { Parser } from '../src/parser.js';
5
- import { PrintStmt, VarStmt, LiteralExpr, VariableExpr } from '../src/ast.js';
5
+ import {
6
+ PrintStmt,
7
+ VarStmt,
8
+ FunctionStmt,
9
+ LiteralExpr,
10
+ VariableExpr,
11
+ BinaryExpr
12
+ } from '../src/ast.js';
6
13
 
7
14
  describe('Parser', () => {
8
15
  test('parse print statement', () => {
@@ -85,4 +92,128 @@ describe('Parser', () => {
85
92
  assert.ok(stmts[0] instanceof VarStmt);
86
93
  assert.ok(stmts[1] instanceof PrintStmt);
87
94
  });
95
+
96
+ test('parse function declaration without parameters', () => {
97
+ const source: string = `कार्य नमस्कार() {
98
+ छाप("नमस्ते")
99
+ }`;
100
+ const lexer: Lexer = new Lexer(source.trim());
101
+ const tokens = lexer.readTokens();
102
+ const parser: Parser = new Parser(tokens);
103
+ const stmts = parser.parse();
104
+
105
+ assert.equal(stmts.length, 1);
106
+ assert.ok(stmts[0] instanceof FunctionStmt);
107
+ const funcStmt = stmts[0] as FunctionStmt;
108
+ assert.equal(funcStmt.name.lexeme, 'नमस्कार');
109
+ assert.equal(funcStmt.params.length, 0);
110
+ assert.equal(funcStmt.body.length, 1);
111
+ });
112
+
113
+ test('parse function declaration with parameters', () => {
114
+ const source: string = `कार्य जोड(अ, ब) {
115
+ छाप(अ)
116
+ छाप(ब)
117
+ }`;
118
+ const lexer: Lexer = new Lexer(source.trim());
119
+ const tokens = lexer.readTokens();
120
+ const parser: Parser = new Parser(tokens);
121
+ const stmts = parser.parse();
122
+
123
+ assert.equal(stmts.length, 1);
124
+ assert.ok(stmts[0] instanceof FunctionStmt);
125
+ const funcStmt = stmts[0] as FunctionStmt;
126
+ assert.equal(funcStmt.name.lexeme, 'जोड');
127
+ assert.equal(funcStmt.params.length, 2);
128
+ assert.equal(funcStmt.params[0]!.lexeme, 'अ');
129
+ assert.equal(funcStmt.params[1]!.lexeme, 'ब');
130
+ assert.equal(funcStmt.body.length, 2);
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
+ });
88
219
  });