Skip to content
This repository was archived by the owner on Oct 13, 2024. It is now read-only.

Commit e53dd8d

Browse files
Merge pull request #38 from SahilK-027/main
Adding Template Literals in AssemblyScript
2 parents fe625e9 + 21d3cac commit e53dd8d

File tree

6 files changed

+210
-25
lines changed

6 files changed

+210
-25
lines changed

BackEnd/Interpreter/interpreter.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
Stmt,
2525
StringLiteral,
2626
SwitchStatement,
27+
TemplateLiteralNode,
2728
UnaryExpr,
2829
VariableDeclaration,
2930
WakandaForStatement,
@@ -34,6 +35,7 @@ import Environment from "../Scope/environment.ts";
3435
import {
3536
BooleanVal,
3637
MAKE_NUll,
38+
MAKE_STRING,
3739
NumberVal,
3840
RuntimeVal,
3941
StringVal,
@@ -100,6 +102,27 @@ const evaluate_identifier = (
100102
return val;
101103
};
102104

105+
const evaluate_template_literals = (
106+
node: TemplateLiteralNode,
107+
env: Environment,
108+
): RuntimeVal => {
109+
let ans = "";
110+
node.body.forEach((element) => {
111+
switch (typeof element) {
112+
case "string":
113+
ans += element;
114+
break;
115+
case "object":
116+
const templateLiteralValue = evaluate(element.value, env).value;
117+
ans += templateLiteralValue.toString();
118+
break;
119+
default:
120+
throw `RunTimeError: Error while interpreting TemplateLiteralNode`;
121+
}
122+
});
123+
return MAKE_STRING(ans);
124+
};
125+
103126
/**
104127
* Evaluates an AST node by handling different node kinds and calling the corresponding evaluation functions.
105128
* @param astNode - The AST node to evaluate.
@@ -208,6 +231,11 @@ export function evaluate(astNode: Stmt, env: Environment): RuntimeVal {
208231
astNode as CompoundAssignmentExpr,
209232
env,
210233
);
234+
case "TemplateLiteral":
235+
return evaluate_template_literals(
236+
astNode as TemplateLiteralNode,
237+
env,
238+
);
211239
default:
212240
// If the AST node has an unknown or unsupported kind, we log an error and exit the program.
213241
console.log(astNode);

FrontEnd/AST.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export type NodeType =
3333
| "StringLiteral"
3434
| "NullLiteral"
3535
| "BooleanLiteral"
36+
| "TemplateLiteral"
3637
// Function-related nodes
3738
| "FunctionParam"
3839
| "FunctionDefinition"
@@ -268,6 +269,14 @@ export interface StringLiteral extends Expr {
268269
value: string;
269270
}
270271

272+
/**
273+
* Represents an AST node for template literals.
274+
*/
275+
export interface TemplateLiteralNode extends Expr {
276+
kind: "TemplateLiteral";
277+
body: (string | Expr)[];
278+
}
279+
271280
/**
272281
* Represents a while statement.
273282
* The WhileStatement interface represents a while statement. It has the following properties:

FrontEnd/Parser.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
StringLiteral,
2828
SwitchCase,
2929
SwitchStatement,
30+
TemplateLiteralNode,
3031
UnaryExpr,
3132
VariableDeclaration,
3233
WakandaForStatement,
@@ -1019,6 +1020,56 @@ export default class Parser {
10191020
} as MinusExpr;
10201021
}
10211022

1023+
/**
1024+
* Parses a template literal.
1025+
*
1026+
* @returns An Expr object representing the parsed template literal.
1027+
* @throws {Error} If there are syntax errors or missing tokens in the expression.
1028+
*/
1029+
private parse_template_literal(): Expr {
1030+
const parts: (string | TemplateLiteralInterpolation)[] = [];
1031+
this.expect(
1032+
TokenType.BackTicks,
1033+
"Expected template literal to start with a backtick (`)",
1034+
);
1035+
1036+
while (this.at().type !== TokenType.BackTicks) {
1037+
if (this.at().type === TokenType.Interpolation) {
1038+
this.eat();
1039+
this.expect(
1040+
TokenType.OpenBrace,
1041+
"Expected template literal to start with a backtick '{'",
1042+
);
1043+
if (this.at().type === TokenType.CloseBrace) {
1044+
this.eat();
1045+
continue;
1046+
} else {
1047+
const interpolation = this.parse_expr();
1048+
parts.push({
1049+
value: interpolation,
1050+
kind: interpolation.kind,
1051+
});
1052+
this.expect(
1053+
TokenType.CloseBrace,
1054+
"Expected '}'",
1055+
);
1056+
}
1057+
} else if (this.at().type === TokenType.String) {
1058+
parts.push(this.at().value);
1059+
this.eat();
1060+
} else {
1061+
// Handle other tokens inside template literals
1062+
throw `SyntaxError:line:${this.at().curr_line}: Unexpected token found inside template literal`;
1063+
}
1064+
}
1065+
1066+
this.expect(
1067+
TokenType.BackTicks,
1068+
"Expected template literal to end with a backtick (`)",
1069+
);
1070+
1071+
return { kind: "TemplateLiteral", body: parts } as TemplateLiteralNode;
1072+
}
10221073
/**
10231074
* Parses a primary expression.
10241075
*
@@ -1075,6 +1126,10 @@ export default class Parser {
10751126
case TokenType.NotOperator:
10761127
return this.parse_not_expr();
10771128

1129+
// Handle template literals
1130+
case TokenType.BackTicks:
1131+
return this.parse_template_literal();
1132+
10781133
default:
10791134
throw `SyntaxError:line:${this.at().curr_line}: Unexpected token found while parsing scanned ${this.at().value}`;
10801135
}

FrontEnd/lexer.ts

Lines changed: 112 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export enum TokenType {
77
Number,
88
Identifier,
99
String,
10+
TemplateLiteral, // @{}
1011
// Keywords
1112
NewAvenger,
1213
NewEternal,
@@ -44,6 +45,8 @@ export enum TokenType {
4445
CloseBrace, // }
4546
OpenBracket, // [
4647
CloseBracket, // ]
48+
BackTicks, //]
49+
Interpolation, //@
4750
//Easter eggs
4851
If,
4952
Else,
@@ -297,8 +300,7 @@ export function tokenize(sourceCode: string): Token[] {
297300
const token = getMultiCharacterToken(src);
298301
if (token) {
299302
tokens.push(token);
300-
} // Check for identifiers
301-
else if (isAlphabet(src[0])) {
303+
} else if (isAlphabet(src[0])) {
302304
let id = "";
303305
while (src.length > 0 && isAlphabet(src[0])) {
304306
id += src.shift();
@@ -323,8 +325,114 @@ export function tokenize(sourceCode: string): Token[] {
323325
else if (isSkippable(src[0])) {
324326
// Skip the current character
325327
src.shift();
326-
} // Handle unrecognized characters
327-
else {
328+
} else if (src[0] === "`") {
329+
tokens.push(getToken(src.shift(), TokenType.BackTicks, line_cnt));
330+
while (src.length > 0 && src[0] !== "`") {
331+
if (src[0] === "@") {
332+
tokens.push(
333+
getToken(src.shift(), TokenType.Interpolation, line_cnt),
334+
);
335+
if (src[0] === "{") {
336+
tokens.push(getToken(src.shift(), TokenType.OpenBrace, line_cnt));
337+
while (src.length > 0 && src[0] !== "}") {
338+
if (src[0] === "@") {
339+
break;
340+
} else if (src[0] === "{") {
341+
throw `SyntaxError:line:${line_cnt}: nesting within template literal is not allowed.`;
342+
} else if (src[0] === "`") {
343+
throw `SyntaxError:line:${line_cnt}: Missing closing '}`;
344+
} else if (src[0] === "(") {
345+
tokens.push(
346+
getToken(src.shift(), TokenType.OpenParen, line_cnt),
347+
);
348+
} else if (src[0] === ")") {
349+
tokens.push(
350+
getToken(src.shift(), TokenType.CloseParen, line_cnt),
351+
);
352+
} else if (src[0] === "[") {
353+
tokens.push(
354+
getToken(src.shift(), TokenType.OpenBracket, line_cnt),
355+
);
356+
} else if (src[0] === "]") {
357+
tokens.push(
358+
getToken(src.shift(), TokenType.CloseBracket, line_cnt),
359+
);
360+
} else if (src[0] === ";") {
361+
tokens.push(
362+
getToken(src.shift(), TokenType.Semicolon, line_cnt),
363+
);
364+
} else if (src[0] === "!") {
365+
tokens.push(
366+
getToken(src.shift(), TokenType.NotOperator, line_cnt),
367+
);
368+
} else if (src[0] === ":") {
369+
tokens.push(getToken(src.shift(), TokenType.Colon, line_cnt));
370+
} else if (src[0] === ",") {
371+
tokens.push(getToken(src.shift(), TokenType.Comma, line_cnt));
372+
} else if (src[0] === ".") {
373+
tokens.push(getToken(src.shift(), TokenType.Dot, line_cnt));
374+
} else {
375+
const token = getMultiCharacterToken(src);
376+
if (token) {
377+
tokens.push(token);
378+
} else if (isAlphabet(src[0])) {
379+
let id = "";
380+
while (src.length > 0 && isAlphabet(src[0])) {
381+
id += src.shift();
382+
}
383+
// Check for keywords
384+
const reserved: TokenType = KEYWORDS[id];
385+
if (typeof reserved === "number") {
386+
tokens.push(getToken(id, reserved, line_cnt));
387+
} else {
388+
// unreserved means user defined identifier
389+
tokens.push(getToken(id, TokenType.Identifier, line_cnt));
390+
}
391+
} // Build number token
392+
else if (isNum(src[0])) {
393+
let num = "";
394+
while (src.length > 0 && isNum(src[0])) {
395+
num += src.shift();
396+
}
397+
398+
tokens.push(getToken(num, TokenType.Number, line_cnt));
399+
} // Skip the skippable character
400+
else if (isSkippable(src[0])) {
401+
// Skip the current character
402+
src.shift();
403+
}
404+
}
405+
}
406+
if (src[0] === "}") {
407+
tokens.push(
408+
getToken(src.shift(), TokenType.CloseBrace, line_cnt),
409+
);
410+
} else {
411+
throw `SyntaxError:line:${line_cnt}: missing terminating '}' character.`;
412+
}
413+
} else {
414+
throw `SyntaxError:line:${line_cnt}: missing opening '{' character for template interpolation.`;
415+
}
416+
} else {
417+
let templateString: string = "";
418+
while (src.length > 0 && src[0] !== "@" && src[0] !== "`") {
419+
templateString += src.shift();
420+
}
421+
if (src[0] === "@") {
422+
tokens.push(getToken(templateString, TokenType.String, line_cnt));
423+
} else if (src[0] === "`") {
424+
tokens.push(getToken(templateString, TokenType.String, line_cnt));
425+
break;
426+
}
427+
}
428+
}
429+
if (src[0] === "`") {
430+
tokens.push(getToken(src.shift(), TokenType.BackTicks, line_cnt));
431+
} else {
432+
let backtick = "`";
433+
throw `SyntaxError:line:${line_cnt}: missing closing ${backtick} character for template interpolation.`;
434+
}
435+
} else {
328436
throw `SyntaxError:line:${line_cnt}: Unrecognised character ${
329437
src[0]
330438
} found.`;
@@ -334,6 +442,5 @@ export function tokenize(sourceCode: string): Token[] {
334442

335443
// Push EOF token
336444
tokens.push({ type: TokenType.EOF, value: "EndOfFile", curr_line: line_cnt });
337-
338445
return tokens;
339446
}

feat.avenger

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,6 @@
11
newAvenger i = 27;
2-
newAvenger j = 27;
3-
newAvenger k = 27;
4-
newAvenger l = 27;
5-
newAvenger m = 27;
6-
newAvenger n = 27;
7-
8-
i += 3;
9-
j -= 3;
10-
k *= 3;
11-
l /= 3;
12-
m %= 10;
13-
n ^= 2;
14-
15-
vision(i);
16-
vision(j);
17-
vision(k);
18-
vision(l);
19-
vision(m);
20-
vision(n);
2+
newEternal PI = 3.142;
3+
newAvenger template = `The value of i is: @{i} and the value of PI is @{PI}`;
4+
vision(template);
5+
vision(`Hello how are you. @{ }`);
6+
vision(`The value of 22/7 is @{22/7}`);

main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ async function __run(inputFile: string) {
1313
const program = parser.produceAST(input);
1414
evaluate(program, env);
1515
}
16-
__run("./test.avenger");
16+
__run("./feat.avenger");
1717
/**
1818
* Initializes the script execution.
1919
*/

0 commit comments

Comments
 (0)