Skip to content

Commit

Permalink
Merge pull request #8 from wraith4081/User-Defined-Structures-Objects
Browse files Browse the repository at this point in the history
User Defined Structures & Objects - Development Progress 1
  • Loading branch information
wraith4081 authored Aug 15, 2023
2 parents 370a2c0 + d5d1499 commit 9ae4d2f
Show file tree
Hide file tree
Showing 9 changed files with 204 additions and 30 deletions.
9 changes: 8 additions & 1 deletion src/example/test.wrsc
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
let x = 92 * (3 + 4) / 2
let a = 5419351;
let b = 1725033;

let pi = {
a,
b,
pi: a / b
};
21 changes: 21 additions & 0 deletions src/frontend/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ export type NodeType =

// EXPRESSIONS
| "AssignmentExpression"

// LITERALS
| "Property"
| "ObjectLiteral"
| "NumericLiteral"
| "Identifier"
| "BinaryExpression";
Expand Down Expand Up @@ -75,4 +79,21 @@ export interface Identifier extends Expression {
export interface NumericLiteral extends Expression {
kind: "NumericLiteral";
value: number;
}

/**
* Represents a property in the AST.
*/
export interface Property extends Expression {
kind: "Property";
key: string;
value?: Expression;
}

/**
* Represents an object literal in the AST.
*/
export interface ObjectLiteral extends Expression {
kind: "ObjectLiteral";
properties: Property[];
}
20 changes: 16 additions & 4 deletions src/frontend/lexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,15 @@ export enum TokenType {
Const,

// Grouping / Operators
BinaryOperator,
Equals,
Semicolon,
OpenParentesis,
CloseParentesis,
BinaryOperator,
Comma,
Colon,
OpenParentesis, // (
CloseParentesis, // )
OpenBracket, // {
CloseBracket, // }

// Misc
EOF,
Expand Down Expand Up @@ -74,7 +78,7 @@ export function isInteger(char: string): boolean {
* @returns True if the character is a whitespace character, false otherwise.
*/
export function isSkipable(char: string): boolean {
return [' ', '\n', '\t'].includes(char);
return [' ', '\n', '\t', '\r'].includes(char);
}

/**
Expand All @@ -91,12 +95,20 @@ export function tokenize(sourceCode: string): Token[] {
tokens.push(token(src.shift()!, TokenType.OpenParentesis));
} else if (src[0] === ')') {
tokens.push(token(src.shift()!, TokenType.CloseParentesis));
} else if (src[0] === '{') {
tokens.push(token(src.shift()!, TokenType.OpenBracket));
} else if (src[0] === '}') {
tokens.push(token(src.shift()!, TokenType.CloseBracket));
} else if (['+', '-', '*', '/', '%'].includes(src[0])) {
tokens.push(token(src.shift()!, TokenType.BinaryOperator));
} else if (src[0] === '=') {
tokens.push(token(src.shift()!, TokenType.Equals));
} else if (src[0] === ';') {
tokens.push(token(src.shift()!, TokenType.Semicolon));
} else if (src[0] === ':') {
tokens.push(token(src.shift()!, TokenType.Colon));
} else if (src[0] === ',') {
tokens.push(token(src.shift()!, TokenType.Comma));
} else {
// Handle multi-char tokens

Expand Down
56 changes: 54 additions & 2 deletions src/frontend/parser.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { warn } from '../runtime/log';
import { Statement, Program, Expression, BinaryExpression, NumericLiteral, Identifier, VariableDeclaration, AssignmentExpression } from './ast';
import { Statement, Program, Expression, BinaryExpression, NumericLiteral, Identifier, VariableDeclaration, AssignmentExpression, Property, ObjectLiteral } from './ast';
import { tokenize, Token, TokenType } from './lexer';

/**
Expand Down Expand Up @@ -133,7 +133,7 @@ export default class Parser {
* @returns The parsed assignment expression.
*/
private parseAssignmentExpression(): Expression {
const left = this.parseAdditiveExpression(); // TODO: Switch this into ObjectExpression
const left = this.parseObjectExpression(); // TODO: Switch this into ObjectExpression

if (this.at().type === TokenType.Equals) {
this.eatToken(); // Advance past the '='
Expand All @@ -148,6 +148,58 @@ export default class Parser {
return left;
}

/**
* Parses an object literal expression and returns an AST node representing the expression.
* @returns The AST node representing the object literal expression.
* @throws If an unexpected token is encountered while parsing the expression.
*/
private parseObjectExpression(): Expression {

if (this.at().type !== TokenType.OpenBracket) {
return this.parseAdditiveExpression();
}

this.eatToken(); // Advance past the '{'
const properties = new Array<Property>();

while (this.notEOF() && this.at().type !== TokenType.CloseBracket) {
const key = this.except(TokenType.Identifier, "Unexpected token founded inside object expression. Expected identifier").value;

if (this.at().type === TokenType.Comma) {
this.eatToken();
properties.push({
kind: 'Property',
key
});

continue;
} else if (this.at().type === TokenType.CloseBracket) {
properties.push({
kind: 'Property',
key
});
break;
}

this.except(TokenType.Colon, "Unexpected token founded inside object expression. Expected colon");
const value = this.parseExpression();

properties.push({
kind: 'Property',
key,
value
});

if (this.at().type !== TokenType.CloseBracket) {
this.except(TokenType.Comma, "Unexpected token founded inside object expression. Expected comma");
}

}

this.except(TokenType.CloseBracket, "Unexpected token founded inside object expression. Expected closing brace");
return { kind: "ObjectLiteral", properties } as ObjectLiteral;
}

/**
* Parses an additive expression.
* @returns The parsed additive expression.
Expand Down
59 changes: 43 additions & 16 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,19 @@
import { createInterface } from 'node:readline/promises';
import { stdin as input, stdout as output } from 'node:process';
import fs from 'fs';

// Create a readline interface for user input/output.
const rl = createInterface({ input, output });

import Parser from "./frontend/parser";
import { evaluate } from './runtime/interpreter';
import Environment from './runtime/environment';
import { MK_NULL, MK_NUMBER, MK_BOOL } from './runtime/values';

// Define some initial values for the environment
const environments = {
'x': MK_NUMBER(31),
'true': MK_BOOL(true),
'false': MK_BOOL(false),
'null': MK_NULL()
};
import { createGlobalEnvironment } from './runtime/environment';

// Define an async function for the REPL
!(async function repl() {
async function repl() {
// Create a new parser and environment for each loop
const parser = new Parser();
const env = new Environment();

// Declare the initial environment values
Object.entries(environments).forEach(key => env.declare(...key));
const env = createGlobalEnvironment();

// Print a welcome message
console.log(`\nWraithScript REPL v0.0.1dev`);
Expand All @@ -47,4 +36,42 @@ const environments = {
const result = evaluate(program, env);
console.log(result);
}
})();
}

/**
* Runs a WraithScript file.
* @param filename The path to the file to run.
*/
async function run(filename: string) {
const parser = new Parser();
const env = createGlobalEnvironment();

// Read the file
const file = fs.readFileSync(filename, 'utf-8');

// Parse the file into an AST
const program = parser.produceAST(file);

// Evaluate the AST and print the result
const result = evaluate(program, env);
console.log(result);

// Exit with a status code of 0
process.exit(0);
}

/**
* Parses command-line arguments and runs the appropriate command.
*/
function main(): void {
if (process.argv.length === 2) {
repl();
} else if (process.argv.length === 3) {
run(process.argv[2]);
} else {
console.log('Usage: wraithscript [filename]');
process.exit(1);
}
}

main();
19 changes: 18 additions & 1 deletion src/runtime/environment.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
import { warn } from "./log";
import { RuntimeValue } from "./values";
import { MK_BOOL, MK_NULL, RuntimeValue } from "./values";

/**
* Creates a new global environment with pre-defined values for true, false, and null.
* @returns The new global environment.
*/
export function createGlobalEnvironment() {
const env = new Environment();

Object.entries({
'true': MK_BOOL(true),
'false': MK_BOOL(false),
'null': MK_NULL()
}).forEach(key => env.declare(...key));

return env;
}

/**
* Represents an environment for variable and constant declarations.
Expand All @@ -21,6 +37,7 @@ export default class Environment {
this.constants = new Set();

this.throwErrors = throwErrors;

}

/**
Expand Down
30 changes: 28 additions & 2 deletions src/runtime/eval/expressions.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import { AssignmentExpression, BinaryExpression, Identifier } from "../../frontend/ast";
import { AssignmentExpression, BinaryExpression, Identifier, ObjectLiteral } from "../../frontend/ast";
import Environment from "../environment";
import { evaluate } from "../interpreter";
import { MK_NULL, NumberValue, RuntimeValue } from "../values";
import { MK_NULL, NumberValue, ObjectValue, RuntimeValue } from "../values";

/**
* Evaluates a binary expression with two numeric operands and returns a numeric value.
* @param lhs The left-hand side operand of the binary expression.
* @param rhs The right-hand side operand of the binary expression.
* @param operator The operator used in the binary expression.
* @returns The numeric value resulting from the binary expression.
*/
function evaluateNumericBinaryExpression(lhs: NumberValue, rhs: NumberValue, operator: string): NumberValue {
let value: number = 0;

Expand Down Expand Up @@ -75,3 +82,22 @@ export function evaluateAssignment(node: AssignmentExpression, env: Environment,
const name = (node.asignee as Identifier).symbol;
return env.assign(name, evaluate(node.value, env));
}

/**
* Evaluates an object literal expression and returns an object value.
* @param node The object literal AST node to evaluate.
* @param env The environment in which to evaluate the expression.
* @returns The object value represented by the object literal.
*/
export function evaluateObjectExpression(node: ObjectLiteral, env: Environment): RuntimeValue {
const object = { type: "object", properties: new Map() } as ObjectValue;

for (let { key, value } of node.properties) {

const runtimeValue = (value === undefined) ? env.lookup(key) : evaluate(value, env);

object.properties.set(key, runtimeValue);
}

return object;
}
8 changes: 6 additions & 2 deletions src/runtime/interpreter.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { RuntimeValue } from './values';
import { AssignmentExpression, BinaryExpression, Identifier, NumericLiteral, Program, Statement, VariableDeclaration } from '../frontend/ast';
import { AssignmentExpression, BinaryExpression, Identifier, NumericLiteral, ObjectLiteral, Program, Statement, VariableDeclaration } from '../frontend/ast';
import Environment from './environment';
import { evaluateProgram, evaluateVariableDeclaration } from './eval/statements';
import { evaluateAssignment, evaluateBinaryExpression, evaluateIdentifier } from './eval/expressions';
import { evaluateAssignment, evaluateBinaryExpression, evaluateIdentifier, evaluateObjectExpression } from './eval/expressions';

/**
* Evaluates an AST node and returns a runtime value.
Expand All @@ -25,6 +25,10 @@ export function evaluate(ast: Statement, env: Environment): RuntimeValue {
// Evaluate an identifier
return evaluateIdentifier(ast as Identifier, env);

case 'ObjectLiteral':
// Evaluate an object literal
return evaluateObjectExpression(ast as ObjectLiteral, env);

case 'AssignmentExpression':
// Evaluate an assignment expression
return evaluateAssignment(ast as AssignmentExpression, env);
Expand Down
12 changes: 10 additions & 2 deletions src/runtime/values.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* The possible value types.
*/
export type ValueType = "null" | "number" | "boolean";
export type ValueType = "null" | "number" | "boolean" | "object";

/**
* The interface for a runtime value.
Expand Down Expand Up @@ -52,4 +52,12 @@ export interface NumberValue extends RuntimeValue {
* @param value - The number value.
* @returns The number value.
*/
export const MK_NUMBER = (value: number = 0) => ({ type: "number", value }) as NumberValue;
export const MK_NUMBER = (value: number = 0) => ({ type: "number", value }) as NumberValue;

/**
* The interface for an object value.
*/
export interface ObjectValue extends RuntimeValue {
type: "object";
properties: Map<string, RuntimeValue>;
}

0 comments on commit 9ae4d2f

Please sign in to comment.