Skip to content

Commit ed3529c

Browse files
committed
Extend visitors to implement expressions for evaluation (Refs #51)[4]
* tokenizer.ts - bug fix for TokenType.NOT to prevent crash * py-visitor.ts - Added visitCompareExpr, visitBoolOpExpr, visitVariableExpr and visitNoneExpr * py-operators.ts - refined not oprator logic to handle truthiness - added specific errors for module and power operators with to handle lhs or rhs operand of 0
1 parent d9ffc84 commit ed3529c

File tree

3 files changed

+145
-22
lines changed

3 files changed

+145
-22
lines changed

src/cse-machine/py_operators.ts

Lines changed: 63 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,50 @@ export function evaluateBinaryExpression(code: string, command: ExprNS.Expr, con
339339
// handleRuntimeError
340340
return {type: 'error', message: 'Unsupported complex operation'};
341341
} return {type: 'complex', value: result};
342+
}
343+
// bool and numeric operations
344+
else if ((left.type === 'bool' && (right.type === 'number' || right.type === 'bigint' || right.type === 'bool')) ||
345+
(right.type === 'bool' && (left.type === 'number' || left.type === 'bigint' || left.type === 'bool'))) {
346+
347+
const leftNum = left.type === 'bool' ? (left.value ? 1 : 0) : Number(left.value);
348+
const rightNum = right.type === 'bool' ? (right.value ? 1 : 0) : Number(right.value);
349+
let result: number | boolean;
350+
351+
// Arithmetic
352+
if (typeof operator === 'string') {
353+
if (operator === '__py_adder') result = leftNum + rightNum;
354+
else if (operator === '__py_minuser') result = leftNum - rightNum;
355+
else if (operator === '__py_multiplier') result = leftNum * rightNum;
356+
else if (operator === '__py_divider') {
357+
if (rightNum === 0) {
358+
// handleRuntimeError(context, new ZeroDivisionError(code, command, context));
359+
return { type: 'error', message: 'Division by zero' };
360+
}
361+
result = leftNum / rightNum;
362+
}
363+
else if (operator === '__py_powerer') result = leftNum ** rightNum;
364+
else if (operator === '__py_modder') result = pythonMod(leftNum, rightNum);
365+
else {
366+
// handleRuntimeError(context, new UnsupportedOperandTypeError(code, command, originalLeftType, originalRightType, operand));
367+
return { type: 'error', message: 'Unsupported boolean/numeric operation' };
368+
}
369+
const resultType = (left.type === 'number' || right.type === 'number') ? 'number' : 'bigint';
370+
return { type: resultType, value: resultType === 'bigint' ? BigInt(result) : result };
371+
}
372+
// Comparisons
373+
else {
374+
if (operator === TokenType.GREATER) result = leftNum > rightNum;
375+
else if (operator === TokenType.GREATEREQUAL) result = leftNum >= rightNum;
376+
else if (operator === TokenType.LESS) result = leftNum < rightNum;
377+
else if (operator === TokenType.LESSEQUAL) result = leftNum <= rightNum;
378+
else if (operator === TokenType.DOUBLEEQUAL) result = leftNum === rightNum;
379+
else if (operator === TokenType.NOTEQUAL) result = leftNum !== rightNum;
380+
else {
381+
// handleRuntimeError(context, new UnsupportedOperandTypeError(code, command, originalLeftType, originalRightType, operand));
382+
return { type: 'error', message: 'Unsupported boolean/numeric comparison' };
383+
}
384+
return { type: 'bool', value: result };
385+
}
342386
}
343387
// Float and or Int Operations
344388
else if ((left.type === 'number' || left.type === 'bigint') && (right.type === 'number' || right.type === 'bigint')) {
@@ -360,10 +404,15 @@ export function evaluateBinaryExpression(code: string, command: ExprNS.Expr, con
360404
result = leftFloat / rightFloat;
361405
}
362406
else if (operator === '__py_powerer') result = leftFloat ** rightFloat;
363-
else if (operator === '__py_modder') result = pythonMod(leftFloat, rightFloat);
364-
else {
407+
else if (operator === '__py_modder') {
408+
if (rightFloat === 0) {
409+
// handleRuntimeError(context, new UnsupportedOperandTypeError(code, command, originalLeftType, originalRightType, operand));
410+
return { type: 'error', message: 'Division by zero' };
411+
}
412+
result = pythonMod(leftFloat, rightFloat);
413+
} else {
365414
// handleRuntimeError(context, new UnsupportedOperandTypeError(code, command, originalLeftType, originalRightType, operand));
366-
return { type: 'error', message: 'Unsupported float operation' };
415+
return { type: 'error', message: 'Unsupported float comparison' };
367416
}
368417
return { type: 'number', value: result };
369418
}
@@ -381,10 +430,10 @@ export function evaluateBinaryExpression(code: string, command: ExprNS.Expr, con
381430
return { type: 'error', message: 'Unsupported float comparison' };
382431
}
383432
return { type: 'bool', value: result };
384-
}
433+
}
385434
}
386435
// Same type Integer Operations
387-
else {
436+
else if (left.type === 'bigint' && right.type ==='bigint') {
388437
const leftBigInt = left.value as bigint;
389438
const rightBigInt = right.value as bigint;
390439
let result: bigint | boolean;
@@ -404,13 +453,17 @@ export function evaluateBinaryExpression(code: string, command: ExprNS.Expr, con
404453
} else if (operator === '__py_powerer') {
405454
if (leftBigInt === 0n && rightBigInt < 0) {
406455
// handleRunTimeError, zerodivision error
407-
return { type: 'error', message: '0.0 cannot be raised to a negative pwoer'}
456+
return { type: 'error', message: '0.0 cannot be raised to a negative power'}
408457
}
409458
if (rightBigInt < 0) {
410459
return {type: 'number', value: Number(leftBigInt) ** Number(rightBigInt) };
411460
}
412461
return { type: 'bigint', value: leftBigInt ** rightBigInt };
413462
} else if (operator === '__py_modder') {
463+
if (rightBigInt === 0n) {
464+
// handleRunTimeError - ZeroDivisionError
465+
return { type: 'error', message: 'integer modulo by zero' } ;
466+
}
414467
return { type: 'bigint', value: pythonMod(leftBigInt, rightBigInt) };
415468
} else if (operator === TokenType.GREATER) {
416469
return { type: 'bool', value: leftBigInt > rightBigInt };
@@ -424,10 +477,9 @@ export function evaluateBinaryExpression(code: string, command: ExprNS.Expr, con
424477
return { type: 'bool', value: leftBigInt === rightBigInt };
425478
} else if (operator === TokenType.NOTEQUAL) {
426479
return { type: 'bool', value: leftBigInt !== rightBigInt };
427-
} else {
428-
// handleRuntimeError
429-
return { type: 'error', message: 'Unsupported integer operation' };
480+
}
481+
// handleRuntimeError
482+
return { type: 'error', message: 'Unsupported operation' };
430483
}
431484
}
432-
}
433-
}
485+
}

src/cse-machine/py_visitor.ts

Lines changed: 81 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,12 @@ export class PyVisitor implements ExprNS.Visitor<Value>, StmtNS.Visitor<Value> {
7070
return { type: 'undefined'};
7171
}
7272

73-
visitUnaryExpr(expr: ExprNS.Unary): any {
73+
visitUnaryExpr(expr: ExprNS.Unary): Value {
7474
const argumentValue = this.visit(expr.right);
7575
return evaluateUnaryExpression(expr.operator.type, argumentValue, expr, this.context);
7676
}
7777

78-
visitBinaryExpr(expr: ExprNS.Binary): any {
78+
visitBinaryExpr(expr: ExprNS.Binary): Value {
7979
const leftValue = this.visit(expr.left);
8080
const rightValue = this.visit(expr.right);
8181
const operatorForPyOperators = this.mapOperatorToPyOperator(expr.operator);
@@ -90,33 +90,104 @@ export class PyVisitor implements ExprNS.Visitor<Value>, StmtNS.Visitor<Value> {
9090
)
9191
}
9292

93-
visitBigIntLiteralExpr(expr: ExprNS.BigIntLiteral): any {
93+
visitBigIntLiteralExpr(expr: ExprNS.BigIntLiteral): Value {
9494
return {
9595
type: 'bigint',
9696
value: BigInt(expr.value)
9797
};
9898
}
9999

100100
// Placeholder for TODO expr visitors
101-
visitCompareExpr(expr: ExprNS.Compare): any { /* TODO */ }
102-
visitBoolOpExpr(expr: ExprNS.BoolOp): any { /* TODO */ }
103-
visitGroupingExpr(expr: ExprNS.Grouping): any { return this.visit(expr.expression);}
101+
// To test on multiple comparisons, eg, a < b < c
102+
visitCompareExpr(expr: ExprNS.Compare): Value {
103+
const leftValue = this.visit(expr.left);
104+
const rightValue = this.visit(expr.right);
105+
const operatorToken = expr.operator;
106+
107+
const operatorForEval = this.mapOperatorToPyOperator(operatorToken);
108+
109+
return evaluateBinaryExpression(
110+
this.code,
111+
expr,
112+
this.context,
113+
operatorForEval,
114+
leftValue,
115+
rightValue,
116+
);
117+
}
118+
119+
visitBoolOpExpr(expr: ExprNS.BoolOp): Value {
120+
const leftValue = this.visit(expr.left);
121+
// Handle 'or' short-circuiting
122+
if (expr.operator.type === TokenType.OR) {
123+
let isTruthy = true;
124+
if (leftValue.type === 'bool' && !leftValue.value) isTruthy = false;
125+
if (leftValue.type === 'bigint' && leftValue.value === 0n) isTruthy = false;
126+
if (leftValue.type === 'number' && leftValue.value === 0) isTruthy = false;
127+
if (leftValue.type === 'string' && leftValue.value === '') isTruthy = false;
128+
if (leftValue.type === 'undefined') isTruthy = false;
129+
130+
if (isTruthy) {
131+
return leftValue;
132+
} else {
133+
return this.visit(expr.right);
134+
}
135+
}
136+
// Handle 'and' short-circuiting
137+
if (expr.operator.type === TokenType.AND) {
138+
let isFalsy = false;
139+
if (leftValue.type === 'bool' && !leftValue.value) isFalsy = true;
140+
if (leftValue.type === 'bigint' && leftValue.value === 0n) isFalsy = true;
141+
if (leftValue.type === 'number' && leftValue.value === 0) isFalsy = true;
142+
if (leftValue.type === 'string' && leftValue.value === '') isFalsy = true;
143+
if (leftValue.type === 'undefined') isFalsy = true;
144+
145+
if (isFalsy) {
146+
return leftValue;
147+
} else {
148+
return this.visit(expr.right);
149+
}
150+
}
151+
return { type: 'error', message: 'Unsupported boolean operator' };
152+
}
153+
154+
visitGroupingExpr(expr: ExprNS.Grouping): any {
155+
return this.visit(expr.expression);
156+
}
157+
104158
visitTernaryExpr(expr: ExprNS.Ternary): any { /* TODO */ }
105159
visitLambdaExpr(expr: ExprNS.Lambda): any { /* TODO */ }
106160
visitMultiLambdaExpr(expr: ExprNS.MultiLambda): any { /* TODO */ }
107-
visitVariableExpr(expr: ExprNS.Variable): any { /* TODO */ }
161+
162+
visitVariableExpr(expr: ExprNS.Variable): Value {
163+
const name = expr.name.lexeme;
164+
if (name === 'True') {
165+
return { type: 'bool', value: true };
166+
} else if (name === 'False') {
167+
return { type: 'bool', value: false };
168+
} else if (name === 'None') {
169+
return { type: 'undefined' };
170+
}
171+
// TODO: add user defined variables, for now all variables are caught as error
172+
return { type: 'error', message: `name '${name}' is not defined` };
173+
}
174+
108175
visitCallExpr(expr: ExprNS.Call): any { /* TODO */ }
176+
109177
visitComplexExpr(expr: ExprNS.Complex): Value {
110178
return {
111179
type: 'complex',
112180
value: new PyComplexNumber(expr.value.real, expr.value.imag)
113181
};
114182
}
115-
visitNoneExpr(expr: ExprNS.None): any { /* TODO */ }
183+
184+
visitNoneExpr(expr: ExprNS.None): Value {
185+
return { type: 'undefined' };
186+
}
116187

117188
// Statement Visitors
118-
visitFileInputStmt(stmt: StmtNS.FileInput): any {
119-
let lastValue: any;
189+
visitFileInputStmt(stmt: StmtNS.FileInput): Value {
190+
let lastValue: Value = { type: 'undefined' };
120191
for (const statement of stmt.statements) {
121192
lastValue = this.visit(statement);
122193
}

src/tokenizer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,7 @@ export class Tokenizer {
465465
const previousToken = this.tokens[this.tokens.length - 1];
466466
switch (specialIdent) {
467467
case TokenType.NOT:
468-
if (previousToken.type === TokenType.IS) {
468+
if (previousToken && previousToken.type === TokenType.IS) {
469469
this.overwriteToken(TokenType.ISNOT);
470470
} else {
471471
this.addToken(specialIdent);

0 commit comments

Comments
 (0)