feat: conditions
This commit is contained in:
parent
c1fcc0d1fe
commit
bfa680dd06
36
src/decorators/condition.decorator.ts
Normal file
36
src/decorators/condition.decorator.ts
Normal file
@ -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');
|
||||
}
|
||||
}
|
51
src/decorators/condition.test.ts
Normal file
51
src/decorators/condition.test.ts
Normal file
@ -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);
|
||||
}
|
||||
|
28
src/visitors/unary.visitor.ts
Normal file
28
src/visitors/unary.visitor.ts
Normal file
@ -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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user