feat: conditions

This commit is contained in:
Michal Szczepanski 2023-05-05 22:11:49 +02:00
parent c1fcc0d1fe
commit bfa680dd06
10 changed files with 198 additions and 141 deletions

@ -0,0 +1,36 @@
import {BinaryVisitor} from "../visitors/binary.visitor";
import {BlockStatement, IfStatement} from "../node-types";
import {JsInterpreter} from "../js.interpreter";
import {Logger} from "../logger";
export class ConditionDecorator {
constructor(private condition: IfStatement, private ctx: JsInterpreter) {
}
run() {
Logger.debug('ConditionDecorator->start');
if (BinaryVisitor.visit(this.condition.test, this.ctx.varCache)) {
const js = new JsInterpreter(this.condition.type, this.ctx);
js.run(this.condition.consequent.body)
} else if (this.condition.alternate) {
const alt = this.condition.alternate;
switch (alt.type) {
case 'BlockStatement': {
const js = new JsInterpreter(alt.type, this.ctx);
js.run((alt as BlockStatement).body);
break;
}
case 'IfStatement': {
const c = new ConditionDecorator(alt as IfStatement, this.ctx);
c.run();
break;
}
default: {
console.log('not supported ConditionDecorator->run', alt);
break;
}
}
}
Logger.debug('ConditionDecorator->end');
}
}

@ -0,0 +1,51 @@
import { JsInterpreter } from '../js.interpreter';
import { Program } from '../node-types';
import acorn from 'acorn';
describe('condition.test', () => {
test('condition if', () => {
const code = `
let a = 1;
let b = 2;
if (a+b === 3 || a-b == -1 && a < b) {
b = 10;
}
`;
const ast = acorn.parse(code, { ecmaVersion: 6 });
const js = new JsInterpreter();
js.run((ast as Program).body);
expect(js.varCache.get('b')).toEqual('10')
});
test('condition if->else if->else', () => {
const code = `
let a = 1;
let b = 2;
if (a+b === 3 || a-b == -1 && a < b) {
var c = [];
} else if (a*b === 2) {
var d = [];
} else {
var e = [];
}
`;
const ast = acorn.parse(code, { ecmaVersion: 6 });
const js = new JsInterpreter();
js.run((ast as Program).body);
});
test('condition if->else', () => {
const code = `
let a = 1;
let b = 2;
if (a+b === 3 || a-b == -1 && a < b) {
var c = [];
} else {
var e = [];
}
`;
const ast = acorn.parse(code, { ecmaVersion: 6 });
const js = new JsInterpreter();
js.run((ast as Program).body);
});
});

@ -5,7 +5,7 @@ import { Logger } from '../logger';
import { UpdateVisitor } from '../visitors/update.visitor';
export class LoopDecorator {
constructor(private loop: ForStatement, private ctx?: JsInterpreter) {}
constructor(private loop: ForStatement, private ctx: JsInterpreter) {}
run() {
Logger.debug('LoopDecorator->start');

@ -15,4 +15,11 @@ export class VariableDecorator {
this.vars[key] = cache.vars[key];
}
}
refresh(cache: VariableDecorator) {
const keys = Object.keys(cache.vars);
for (const key of keys) {
if (this.vars[key]) this.vars[key] = cache.vars[key];
}
}
}

@ -23,21 +23,4 @@ fibonacci(16, result);
js.run((ast as Program).body);
expect(js.varCache.get('result').arr).toEqual(['1', '1', 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987]);
});
test('calculate condition', () => {
const code = `
let a = 1;
let b = 2;
if (a+b === 3 || a-b == -1 && a < b) {
var c = [];
} else if (a*b === 2) {
var d = [];
} else {
var e = [];
}
`;
const ast = acorn.parse(code, { ecmaVersion: 6 });
const js = new JsInterpreter();
js.run((ast as Program).body);
});
});

@ -1,4 +1,4 @@
import { ExpressionStatement, ForStatement, FunctionDeclaration, VariableDeclaration } from './node-types';
import {ExpressionStatement, ForStatement, FunctionDeclaration, IfStatement, VariableDeclaration} from './node-types';
import { ExpressionVisitor } from './visitors/expression.visitor';
import { FunctionDecorator } from './decorators/function.decorator';
import { Logger } from './logger';
@ -6,6 +6,7 @@ import { LoopDecorator } from './decorators/loop.decorator';
import { Node } from 'acorn';
import { VariableDecorator } from './decorators/variable.decorator';
import { VariableSetterVisitor } from './visitors/variable.setter.visitor';
import {ConditionDecorator} from "./decorators/condition.decorator";
export class JsInterpreter {
readonly varCache = new VariableDecorator();
@ -47,7 +48,9 @@ export class JsInterpreter {
break;
}
case 'IfStatement': {
const condition = new ConditionDecorator(node as IfStatement, this);
condition.run();
break;
}
default: {
console.log('Not supported JsInterpreter->run', node);
@ -55,6 +58,8 @@ export class JsInterpreter {
}
}
}
// update modified variables up to ctx
this.ctx?.varCache.refresh(this.varCache);
}
private addVariables(n: VariableDeclaration): void {

@ -3,10 +3,11 @@ import { Node } from 'acorn';
export type Expression = CallExpression | AssignmentExpression;
export type VariableValue = ArrayExpression | Literal | Identifier | BinaryExpression | MemberExpression;
export type Callee = Identifier | MemberExpression;
export type Assignment = Identifier | MemberExpression | Literal;
export type Assignment = Identifier | MemberExpression | Literal | LogicalExpression;
export type Binary = Identifier | MemberExpression | Literal | LogicalExpression | UnaryExpression;
export type MemberProperty = Identifier | Literal;
export type LogicalExpression = BinaryExpression;
export type AssignmentExpression = BinaryExpression;
export type AlternateCondition = BlockStatement | IfStatement;
export interface Program extends Node {
body: Node[];
@ -44,12 +45,18 @@ export interface ArrayExpression extends Node {
elements: [];
}
export interface BinaryExpression extends Node {
export interface AssignmentExpression extends Node {
left: Assignment;
operator: string;
right: Assignment;
}
export interface BinaryExpression extends Node {
left: Binary;
operator: string;
right: Binary;
}
export interface UpdateExpression extends Node {
prefix: boolean;
operator: string;
@ -67,6 +74,11 @@ export interface MemberExpression extends Node {
computed: boolean;
}
export interface UnaryExpression extends Node {
operator: string;
argument: MemberProperty;
}
// --- Statement ---
export interface BlockStatement extends Node {
@ -87,5 +99,5 @@ export interface ForStatement extends Node {
export interface IfStatement extends Node {
test: LogicalExpression;
consequent: BlockStatement;
alternate: BlockStatement;
alternate?: AlternateCondition;
}

@ -1,4 +1,4 @@
import { Assignment, AssignmentExpression, Identifier, MemberExpression } from '../node-types';
import {Assignment, AssignmentExpression, Identifier, MemberExpression, UnaryExpression} from '../node-types';
import { MemberVisitor } from './member.visitor';
import { VariableDecorator } from '../decorators/variable.decorator';
import { VariableGetterVisitor } from './variable.getter.visitor';

@ -1,130 +1,53 @@
import { BinaryExpression, Identifier, Literal, MemberExpression } from '../node-types';
import {
Binary,
BinaryExpression,
Identifier,
Literal,
MemberExpression,
UnaryExpression
} from '../node-types';
import { ArrayIndexVisitor } from './array.index.visitor';
import { Logger } from '../logger';
import { VariableDecorator } from '../decorators/variable.decorator';
import {UnaryVisitor} from "./unary.visitor";
export class BinaryVisitor {
static visit(exp: BinaryExpression, varCache: VariableDecorator): boolean | number {
if (exp.left.type === 'Identifier' && exp.right.type === 'Identifier') {
const leftValue = varCache.get((exp.left as Identifier).name);
const rightValue = varCache.get((exp.right as Identifier).name);
return this.valueLeftRight(leftValue, rightValue, exp.operator);
}
const left = this.getAssignmentValue(exp.left, varCache);
const right = this.getAssignmentValue(exp.right, varCache);
const value = this.computeValue(left, right, exp.operator);
Logger.debug('BinaryVisitor->visit->value', left, exp.operator, right, value);
return value;
}
if (exp.left.type === 'MemberExpression' && exp.right.type === 'MemberExpression') {
return this.memberLeftRight(exp.left as MemberExpression, exp.right as MemberExpression, exp.operator, varCache);
private static getAssignmentValue(assignment: Binary, varCache: VariableDecorator) {
switch (assignment.type) {
case 'Identifier': {
return varCache.get((assignment as Identifier).name);
} case 'Literal': {
return (assignment as Literal).raw;
}
case 'MemberExpression': {
const n = assignment as MemberExpression;
const v = varCache.get(n.object.name);
if (v.__type__ === 'array') {
return ArrayIndexVisitor.visitGet(n.property, v);
}
console.log('not supported BinaryVisitor->getAssignmentValue->MemberExpression', v);
return false;
}
case 'UnaryExpression':
return UnaryVisitor.visit(assignment as UnaryExpression, varCache);
case 'BinaryExpression':
case 'LogicalExpression': {
return BinaryVisitor.visit(assignment as BinaryExpression, varCache);
}
}
if (exp.left.type === 'Identifier' && exp.right.type === 'MemberExpression') {
return this.identifierLeftMemberRight(
exp.left as Identifier,
exp.right as MemberExpression,
exp.operator,
varCache
);
}
if (exp.left.type === 'MemberExpression' && exp.right.type === 'Identifier') {
return this.memberLeftIdentifierRight(
exp.left as MemberExpression,
exp.right as Identifier,
exp.operator,
varCache
);
}
if (exp.left.type === 'MemberExpression' && exp.right.type === 'Literal') {
return this.memberLeftLiteralRight(exp.left as MemberExpression, exp.right as Literal, exp.operator, varCache);
}
if (exp.left.type === 'Literal' && exp.right.type === 'MemberExpression') {
return this.literalLeftMemberRight(exp.left as Literal, exp.right as MemberExpression, exp.operator, varCache);
}
console.log('not supported BinaryVisitor->visit->type', 'left', exp.left.type, 'right', exp.right.type);
console.log('not supported BinaryVisitor->getAssignmentValue', assignment);
return false;
}
private static literalLeftMemberRight(
left: Literal,
right: MemberExpression,
operator: string,
varCache: VariableDecorator
) {
const v = varCache.get(right.object.name);
if (v.__type__ === 'array') {
const rightValue = ArrayIndexVisitor.visitGet(right.property, v);
return this.valueLeftRight(left.raw, rightValue, operator);
}
console.log('not supported BinaryVisitor->literalLeftMemberRight', v);
return false;
}
private static memberLeftLiteralRight(
left: MemberExpression,
right: Literal,
operator: string,
varCache: VariableDecorator
) {
const v = varCache.get(left.object.name);
if (v.__type__ === 'array') {
const leftValue = ArrayIndexVisitor.visitGet(left.property, v);
return this.valueLeftRight(leftValue, right.raw, operator);
}
console.log('not supported BinaryVisitor->memberLeftLiteralRight', v);
return false;
}
private static memberLeftIdentifierRight(
left: MemberExpression,
right: Identifier,
operator: string,
varCache: VariableDecorator
) {
const v = varCache.get(left.object.name);
const rightValue = varCache.get(right.name);
if (v.__type__ === 'array') {
const leftValue = ArrayIndexVisitor.visitGet(left.property, v);
return this.valueLeftRight(leftValue, rightValue, operator);
}
console.log('not supported BinaryVisitor->memberLeftIdentifierRight', v);
return false;
}
private static identifierLeftMemberRight(
left: Identifier,
right: MemberExpression,
operator: string,
varCache: VariableDecorator
) {
const v = varCache.get(right.object.name);
const leftValue = varCache.get(left.name);
if (v.__type__ === 'array') {
const rightValue = ArrayIndexVisitor.visitGet(right.property, v);
return this.valueLeftRight(leftValue, rightValue, operator);
}
console.log('not supported BinaryVisitor->identifierLeftMemberRight', v);
return false;
}
private static memberLeftRight(
left: MemberExpression,
right: MemberExpression,
operator: string,
varCache: VariableDecorator
) {
const vl = varCache.get(left.object.name);
const vr = varCache.get(right.object.name);
if (vl.__type__ === 'array' && vl.__type__ === 'array') {
const leftValue = ArrayIndexVisitor.visitGet(left.property, vl);
const rightValue = ArrayIndexVisitor.visitGet(right.property, vr);
return this.valueLeftRight(leftValue, rightValue, operator);
}
console.log('not supported BinaryVisitor->memberLeftRight', left, right);
return false;
}
private static valueLeftRight(leftValue: any, rightValue: any, operator: string) {
private static computeValue(leftValue: any, rightValue: any, operator: string) {
Logger.debug('BinaryVisitor->valueLeftRight', leftValue, operator, rightValue);
switch (operator) {
case '<': {
@ -142,6 +65,18 @@ export class BinaryVisitor {
case '/': {
return parseFloat(leftValue) / parseFloat(rightValue);
}
case '===': {
return isNaN(leftValue) ? leftValue === rightValue : parseFloat(leftValue) === parseFloat(rightValue);
}
case '==': {
return leftValue === rightValue;
}
case '||': {
return leftValue || rightValue;
}
case '&&': {
return leftValue && rightValue;
}
default: {
console.log('not supported BinaryVisitor->identifierLeftRight->operator', operator);
}

@ -0,0 +1,28 @@
import {Identifier, Literal, MemberProperty, UnaryExpression} from "../node-types";
import {VariableDecorator} from "../decorators/variable.decorator";
export class UnaryVisitor {
static visit(unary: UnaryExpression, varCache: VariableDecorator) {
const value = this.getUnaryValue(unary.argument, varCache);
switch (unary.operator) {
case '-': {
return -parseFloat(value);
}
}
console.log('not supported BinaryVisitor->getAssignmentValue->UnaryExpression', unary);
return false;
}
private static getUnaryValue(argument: MemberProperty, varCache: VariableDecorator) {
switch (argument.type) {
case 'Identifier': {
return varCache.get((argument as Identifier).name);
}
case 'Literal': {
return (argument as Literal).raw;
}
}
console.log('not supported BinaryVisitor->getUnaryArgument', argument);
return false;
}
}