Skip to content

Commit 705d80e

Browse files
committed
groupped logical &comparisson operations in parser
1 parent 2c06833 commit 705d80e

File tree

6 files changed

+136
-52
lines changed

6 files changed

+136
-52
lines changed

src/common/ast-types.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { ExpressionOperators, OperationTypes, Operators } from "./operators";
1+
import { ExpressionOperators, LogicalOperators, OperationTypes, Operators } from "./operators";
22
import { getTokenLoc, getTokenValue, Token } from "./token-types";
33

44
export type AstNodeType = 'assign' | 'binOp' | 'const'
5+
| 'logicalOp'
56
| 'getSingleVar' | 'setSingleVar' | 'dotObjectAccess' | 'bracketObjectAccess'
67
| 'funcCall' | 'funcDef' | 'arrowFuncDef'
78
| 'createObject' | 'createArray'
@@ -182,6 +183,19 @@ export class BracketObjectAccessNode extends AstNode {
182183
}
183184
}
184185

186+
export interface LogicalNodeItem {
187+
node: AstNode,
188+
op: LogicalOperators | undefined
189+
}
190+
191+
export class LogicalOpNode extends AstNode {
192+
constructor(public items: LogicalNodeItem[],
193+
public loc: Uint16Array) {
194+
super('logicalOp');
195+
this.loc = loc;
196+
}
197+
}
198+
185199
export class BinOpNode extends AstNode {
186200
constructor(
187201
public left: AstNode,

src/common/operators.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export enum OperationTypes {
55
export type AssignmentOperators = "=" | "+=" | "-=" | "*=" | "/=" | "++" | "--";
66
export type ArithmeticOperators = "+" | "-" | "*" | "/" | "%" | "**" | "//";
77
export type ComparisonOperators = ">" | ">=" | "==" | "!=" | "<>" | "<" | "<=";
8-
export type LogicalOperators = "and" | "or" | "not" | "not in";
8+
export type LogicalOperators = "and" | "or"; // | "not" | "not in";
99
export type MembershipOperators = "in";
1010

1111
export type Operators = AssignmentOperators | ArithmeticOperators | ComparisonOperators | LogicalOperators | MembershipOperators;
@@ -29,8 +29,8 @@ export const OperatorsMap: Record<Operators, OperationTypes> = {
2929

3030
"and": OperationTypes.Logical,
3131
"or": OperationTypes.Logical,
32-
"not": OperationTypes.Logical,
33-
"not in": OperationTypes.Logical,
32+
// "not": OperationTypes.Logical,
33+
// "not in": OperationTypes.Logical,
3434

3535
"in": OperationTypes.Membership,
3636

@@ -67,8 +67,8 @@ export const OperationFuncs: Record<ExpressionOperators, ExpressionOperation> =
6767

6868
"and": (l, r) => logicalOperation(l, r, "and"),
6969
"or": (l, r) => logicalOperation(l, r, "or"),
70-
"not": (l, r) => logicalOperation(l, r, "not"),
71-
"not in": (l, r) => logicalOperation(l, r, "not in"),
70+
// "not": (l, r) => logicalOperation(l, r, "not"),
71+
// "not in": (l, r) => logicalOperation(l, r, "not in"),
7272

7373
"in": (l, r) => membershipOperation(l, r, "in")
7474
}

src/common/token-types.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Operators, OperatorsMap } from "./operators";
1+
import { OperationTypes, Operators, OperatorsMap } from "./operators";
22

33
export enum TokenTypes {
44
Identifier = 0,
@@ -40,7 +40,7 @@ export function getTokenType(token: Token): TokenTypes {
4040
}
4141

4242
export function getTokenValue(token: Token | null): TokenValue {
43-
return token? token[0] : null;
43+
return token ? token[0] : null;
4444
}
4545

4646
export function getTokenLoc(token: Token): Uint16Array {
@@ -120,8 +120,10 @@ export function findTokenValueIndexes(tokens: Token[], predicate: (value: TokenV
120120
return opIndexes;
121121
}
122122

123-
export function findOperators(tokens: Token[]): number[] {
124-
return findTokenValueIndexes(tokens, value => OperatorsMap[value as Operators] !== undefined);
123+
export function findOperators(tokens: Token[], operationType: OperationTypes | null = null): number[] {
124+
return !operationType ? findTokenValueIndexes(tokens, value => OperatorsMap[value as Operators] !== undefined)
125+
:
126+
findTokenValueIndexes(tokens, value => OperatorsMap[value as Operators] === operationType);
125127
}
126128

127129
function skipInnerBrackets(tokens: Token[], i: number, openChar: string, closeChar: string): number {

src/interpreter.spec.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,10 +291,36 @@ describe('Interpreter', () => {
291291

292292
it('chaining funcCall with null coelsing', () => {
293293
expect(e.eval("p?.f()?.sdsd")).toBe(null);
294-
//expect(e.eval("p?.f()?.sdsd or 5")).toBe(5);
294+
expect(e.eval("p?.f()?.sdsd or 5")).toBe(5);
295295
});
296296

297+
it('comparison operations', () => {
298+
expect(e.eval("1+2*3==7")).toBe(true);
299+
expect(e.eval("1+2==2")).toBe(false);
300+
});
301+
302+
it('comparison operations', () => {
303+
expect(e.eval("1+2*3==7")).toBe(true);
304+
expect(e.eval("1+2==2")).toBe(false);
305+
});
297306

307+
// ** migration issue for now
308+
it('simple and operator', async () => {
309+
expect(await e.evaluate('2 == 2 and 3 == 3')).toBe(true)
310+
// expect(await e.evaluate('(2 == 2) and (3 == 3) and (5 == 5)')).toBe(true)
311+
// expect(await e.evaluate('(2 == 2) and (3 != 3) and 5 == 5)')).toBe(false)
312+
// expect(await e.evaluate('(2 != 2) and (3 != 3) and (5 == 5)')).toBe(false)
313+
// expect(await e.evaluate('(2 != 2) and (3 == 3) and (5 == 5)')).toBe(false)
314+
// expect(await e.evaluate('(2 == 2) and (3 == 3) and 5 != 5)')).toBe(false)
315+
})
316+
317+
it('simple or operator', async () => {
318+
expect(await e.evaluate('2 == 2 or 3 == 3')).toBe(true)
319+
// expect(await e.evaluate('2 == 2 or 3 == 3 or 5 == 5')).toBe(true)
320+
// expect(await e.evaluate('2 != 2 or 3 != 3 or 5 != 5')).toBe(false)
321+
// expect(await e.evaluate('2 == 2 or 3 != 3 or 5 != 5')).toBe(true)
322+
// expect(await e.evaluate('2 == 2 or 3 == 3 and 5 != 5')).toBe(true)
323+
})
298324

299325

300326
});

src/interpreter.v1.spec.ts

Lines changed: 15 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -30,22 +30,21 @@ describe('Interpreter', () => {
3030
expect(await e.evaluate(`"""${text}"""`)).toBe(text);
3131
});
3232

33-
// // *** migration issue ***
34-
// it('Triple quote string as an expression', async () => {
35-
// const text = `
36-
// 1
37-
// 2 3 4
38-
// 5
39-
// `;
40-
// expect(await e.evaluate(`
41-
// str = """ 12345 """
42-
// str
43-
// `)).toBe(' 12345 ');
44-
// expect(await e.evaluate(`
45-
// str = """${text}"""
46-
// str
47-
// `)).toBe(text);
48-
// });
33+
it('Triple quote string as an expression', async () => {
34+
const text = `
35+
1
36+
2 3 4
37+
5
38+
`;
39+
expect(await e.evaluate(`
40+
str = """ 12345 """
41+
str
42+
`)).toBe(' 12345 ');
43+
expect(await e.evaluate(`
44+
str = """${text}"""
45+
str
46+
`)).toBe(text);
47+
});
4948

5049
it('print(add(33, 2))', async () => {
5150
expect(await e.evaluate('print(add(33, 2, 45))'))
@@ -1009,24 +1008,6 @@ describe('Interpreter', () => {
10091008
).toBe("4,9");
10101009
})
10111010

1012-
// ** migration issue for now
1013-
// it('and operator', async () => {
1014-
// expect(await e.evaluate('(2 == 2) and (3 == 3)')).toBe(true)
1015-
// expect(await e.evaluate('(2 == 2) and (3 == 3) and (5 == 5)')).toBe(true)
1016-
// expect(await e.evaluate('(2 == 2) and (3 != 3) and 5 == 5)')).toBe(false)
1017-
// expect(await e.evaluate('(2 != 2) and (3 != 3) and (5 == 5)')).toBe(false)
1018-
// expect(await e.evaluate('(2 != 2) and (3 == 3) and (5 == 5)')).toBe(false)
1019-
// expect(await e.evaluate('(2 == 2) and (3 == 3) and 5 != 5)')).toBe(false)
1020-
// })
1021-
1022-
// it('or operator', async () => {
1023-
// expect(await e.evaluate('2 == 2 or 3 == 3)')).toBe(true)
1024-
// expect(await e.evaluate('2 == 2 or 3 == 3 or 5 == 5)')).toBe(true)
1025-
// expect(await e.evaluate('2 != 2 or 3 != 3 or 5 != 5)')).toBe(false)
1026-
// expect(await e.evaluate('2 == 2 or 3 != 3 or 5 != 5)')).toBe(true)
1027-
// expect(await e.evaluate('2 == 2 or 3 == 3 and 5 != 5)')).toBe(true)
1028-
// })
1029-
10301011
it('(if) - else', async () => {
10311012
expect(await e.evaluate(`
10321013
x = 5
@@ -1574,8 +1555,6 @@ describe('Interpreter', () => {
15741555
expect(o.x[0].d).toBe(5);
15751556
})
15761557

1577-
//
1578-
15791558
it('should return NULL gracely', async () => {
15801559
expect(await e.evaluate(`
15811560
x = {}

src/parser/parser.ts

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {
33
GetSingleVarNode, FunctionCallNode, getTokenType, getTokenValue, isTokenTypeLiteral, getStartLine,
44
getStartColumn, getEndColumn, getEndLine, findOperators, splitTokens, DotObjectAccessNode, BracketObjectAccessNode,
55
findTokenValueIndex, FunctionDefNode, CreateObjectNode, ObjectPropertyInfo, CreateArrayNode, ArrowFuncDefNode,
6-
ExpressionOperators, IfNode, ForNode, WhileNode, ImportNode, NameAlias, ContinueNode, BreakNode, ReturnNode, CommentNode, getTokenLoc
6+
ExpressionOperators, IfNode, ForNode, WhileNode, ImportNode, NameAlias, ContinueNode, BreakNode, ReturnNode, CommentNode, getTokenLoc, OperationTypes, LogicalNodeItem, LogicalOperators, LogicalOpNode, ComparisonOperators
77
} from '../common';
88
import { JspyParserError } from '../common/utils';
99

@@ -66,6 +66,12 @@ export class Parser {
6666
return bodyAst.body;
6767
}
6868

69+
const findIndexes = (tkns: Token[], operation: OperationTypes, result: number[]): boolean => {
70+
result.splice(0, result.length);
71+
findOperators(tkns, operation).forEach(r => result.push(r));
72+
return !!result.length;
73+
}
74+
6975
for (let i = 0; i < instructions.length; i++) {
7076
const instruction = instructions[i];
7177

@@ -85,8 +91,11 @@ export class Parser {
8591
const firstToken = instruction.tokens[0];
8692
const secondToken = instruction.tokens.length > 1 ? instruction.tokens[1] : null;
8793
this._currentToken = firstToken;
94+
95+
const logicOpIndexes: number[] = [];
96+
const comparisonOpIndexs: number[] = [];
97+
const assignTokenIndexes: number[] = [];
8898

89-
const assignTokens = splitTokens(instruction.tokens, '=');
9099

91100
if (getTokenType(firstToken) === TokenTypes.Comment) {
92101
ast.body.push(new CommentNode(getTokenValue(firstToken) as string, getTokenLoc(firstToken)));
@@ -205,14 +214,68 @@ export class Parser {
205214
const body = {} as AstBlock; // empty for now
206215

207216
ast.body.push(new ImportNode(module, body, parts, getTokenLoc(firstToken)))
208-
} else if (assignTokens.length > 1) {
217+
} else if (findIndexes(instruction.tokens, OperationTypes.Assignment, assignTokenIndexes)) {
218+
const assignTokens = splitTokens(instruction.tokens, '=');
209219
const target = this.createExpressionNode(assignTokens[0]);
210220
const source = this.createExpressionNode(assignTokens[1]);
211221
ast.body.push(new AssignNode(target, source, getTokenLoc(assignTokens[0][0])));
222+
} else if (findIndexes(instruction.tokens, OperationTypes.Logical, logicOpIndexes)) {
223+
ast.body.push(this.groupComparisonOperations(logicOpIndexes, instruction));
224+
} else if (findIndexes(instruction.tokens, OperationTypes.Comparison, comparisonOpIndexs)) {
225+
ast.body.push(this.groupComparisonOperations(comparisonOpIndexs, instruction));
212226
} else {
213227
ast.body.push(this.createExpressionNode(instruction.tokens))
214228
}
229+
230+
}
231+
}
232+
233+
private groupComparisonOperations(indexes: number[], instruction: InstructionLine): AstNode {
234+
let start = 0;
235+
const slice = (a: Token[], begin: number, end: number): Token[] => {
236+
// if expression is in brackets, then we need clean brackets
237+
if (getTokenValue(a[begin]) === '(') {
238+
begin++;
239+
end--;
240+
}
241+
242+
return a.slice(begin, end);
243+
}
244+
245+
let leftNode: AstNode | null = null;
246+
for (let i = 0; i < indexes.length; i++) {
247+
const opToken = getTokenValue(instruction.tokens[indexes[i]]) as ComparisonOperators;
248+
leftNode = (leftNode) ? leftNode : this.createExpressionNode(slice(instruction.tokens, start, indexes[i]))
249+
250+
const endInd = (i + 1 < indexes.length) ? indexes[i + 1] : instruction.tokens.length;
251+
const rightNode = this.createExpressionNode(slice(instruction.tokens, indexes[i] + 1, endInd))
252+
253+
leftNode = new BinOpNode(leftNode, opToken, rightNode, getTokenLoc(instruction.tokens[0]));
215254
}
255+
256+
return leftNode as AstNode;
257+
}
258+
259+
private groupLogicalOperations(logicOp: number[], instruction: InstructionLine) {
260+
let start = 0;
261+
const logicItems: LogicalNodeItem[] = [];
262+
for (let i = 0; i < logicOp.length; i++) {
263+
const opToken = instruction.tokens[logicOp[i]];
264+
const logicalSlice = instruction.tokens.slice(start, logicOp[i]);
265+
logicItems.push({
266+
node: this.createExpressionNode(logicalSlice),
267+
op: getTokenValue(opToken) as LogicalOperators
268+
});
269+
270+
start = logicOp[i] + 1;
271+
}
272+
273+
logicItems.push({
274+
node: this.createExpressionNode(instruction.tokens.slice(start))
275+
} as LogicalNodeItem);
276+
277+
const lop = new LogicalOpNode(logicItems, getTokenLoc(instruction.tokens[0]));
278+
return lop;
216279
}
217280

218281
private tokensToInstructionLines(tokens: Token[], startLine: number): InstructionLine[] {
@@ -222,7 +285,7 @@ export class Parser {
222285
let currentLine = startLine;
223286
let line = new InstructionLine();
224287
for (let i = 0; i < tokens.length; i++) {
225-
const token = tokens[i];
288+
const token = tokens[i];
226289
const sLine = getStartLine(token);
227290
const sColumn = getStartColumn(token);
228291
const value = getTokenValue(token);
@@ -304,7 +367,7 @@ export class Parser {
304367
return new ArrowFuncDefNode(funcAst, params, getTokenLoc(tokens[0]));
305368
}
306369

307-
// create expression
370+
// create arithmetic expression
308371
const ops = findOperators(tokens);
309372
if (ops.length) {
310373
// create binary node here

0 commit comments

Comments
 (0)