UNPKG

@neo-one/smart-contract-compiler

Version:

NEO•ONE TypeScript smart contract compiler.

389 lines (387 loc) 19.3 kB
import { tsUtils } from '@neo-one/ts-utils'; import { utils } from '@neo-one/utils'; import ts from 'typescript'; import { isBuiltinInstanceOf } from '../builtins'; import { handleTypeAssignment } from '../helper/types'; import { NodeCompiler } from '../NodeCompiler'; export class BinaryExpressionCompiler extends NodeCompiler { constructor() { super(...arguments); this.kind = ts.SyntaxKind.BinaryExpression; } visitNode(sb, expr, options) { const kind = tsUtils.expression.getOperatorToken(expr).kind; switch (kind) { case ts.SyntaxKind.EqualsToken: case ts.SyntaxKind.PlusEqualsToken: case ts.SyntaxKind.MinusEqualsToken: case ts.SyntaxKind.AsteriskAsteriskEqualsToken: case ts.SyntaxKind.AsteriskEqualsToken: case ts.SyntaxKind.SlashEqualsToken: case ts.SyntaxKind.PercentEqualsToken: case ts.SyntaxKind.AmpersandEqualsToken: case ts.SyntaxKind.BarEqualsToken: case ts.SyntaxKind.CaretEqualsToken: case ts.SyntaxKind.LessThanLessThanEqualsToken: case ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: case ts.SyntaxKind.GreaterThanGreaterThanEqualsToken: this.visitAssignmentOperator(sb, kind, expr, options); break; case ts.SyntaxKind.AsteriskToken: case ts.SyntaxKind.SlashToken: case ts.SyntaxKind.PercentToken: case ts.SyntaxKind.PlusToken: case ts.SyntaxKind.MinusToken: case ts.SyntaxKind.GreaterThanGreaterThanToken: case ts.SyntaxKind.LessThanLessThanToken: case ts.SyntaxKind.LessThanToken: case ts.SyntaxKind.LessThanEqualsToken: case ts.SyntaxKind.GreaterThanToken: case ts.SyntaxKind.GreaterThanEqualsToken: case ts.SyntaxKind.ExclamationEqualsToken: case ts.SyntaxKind.EqualsEqualsToken: case ts.SyntaxKind.AmpersandToken: case ts.SyntaxKind.BarToken: case ts.SyntaxKind.CaretToken: case ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken: case ts.SyntaxKind.InKeyword: case ts.SyntaxKind.InstanceOfKeyword: case ts.SyntaxKind.CommaToken: case ts.SyntaxKind.AsteriskAsteriskToken: case ts.SyntaxKind.EqualsEqualsEqualsToken: case ts.SyntaxKind.ExclamationEqualsEqualsToken: this.visitExpressionOperator(sb, kind, expr, options); break; case ts.SyntaxKind.AmpersandAmpersandToken: case ts.SyntaxKind.BarBarToken: case ts.SyntaxKind.QuestionQuestionToken: this.visitLogicalExpressionOperator(sb, kind, expr, options); break; default: utils.assertNever(kind); } } visitAssignmentOperator(sb, kind, expr, options) { const left = tsUtils.expression.getLeft(expr); const right = tsUtils.expression.getRight(expr); const token = tsUtils.expression.getOperatorToken(expr); const pushValueOptions = sb.pushValueOptions(options); switch (kind) { case ts.SyntaxKind.EqualsToken: sb.visit(right, pushValueOptions); break; case ts.SyntaxKind.PlusEqualsToken: this.visitExpressionOperatorBase(sb, token, ts.SyntaxKind.PlusToken, left, right, pushValueOptions); break; case ts.SyntaxKind.MinusEqualsToken: this.visitExpressionOperatorBase(sb, token, ts.SyntaxKind.MinusToken, left, right, pushValueOptions); break; case ts.SyntaxKind.AsteriskAsteriskEqualsToken: this.visitExpressionOperatorBase(sb, token, ts.SyntaxKind.AsteriskAsteriskToken, left, right, pushValueOptions); break; case ts.SyntaxKind.AsteriskEqualsToken: this.visitExpressionOperatorBase(sb, token, ts.SyntaxKind.AsteriskToken, left, right, pushValueOptions); break; case ts.SyntaxKind.SlashEqualsToken: this.visitExpressionOperatorBase(sb, token, ts.SyntaxKind.SlashToken, left, right, pushValueOptions); break; case ts.SyntaxKind.PercentEqualsToken: this.visitExpressionOperatorBase(sb, token, ts.SyntaxKind.PercentToken, left, right, pushValueOptions); break; case ts.SyntaxKind.AmpersandEqualsToken: this.visitExpressionOperatorBase(sb, token, ts.SyntaxKind.AmpersandToken, left, right, pushValueOptions); break; case ts.SyntaxKind.BarEqualsToken: this.visitExpressionOperatorBase(sb, token, ts.SyntaxKind.BarToken, left, right, pushValueOptions); break; case ts.SyntaxKind.CaretEqualsToken: this.visitExpressionOperatorBase(sb, token, ts.SyntaxKind.CaretToken, left, right, pushValueOptions); break; case ts.SyntaxKind.LessThanLessThanEqualsToken: this.visitExpressionOperatorBase(sb, token, ts.SyntaxKind.LessThanLessThanToken, left, right, pushValueOptions); break; case ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: this.visitExpressionOperatorBase(sb, token, ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken, left, right, pushValueOptions); break; case ts.SyntaxKind.GreaterThanGreaterThanEqualsToken: this.visitExpressionOperatorBase(sb, token, ts.SyntaxKind.GreaterThanGreaterThanToken, left, right, pushValueOptions); break; default: utils.assertNever(kind); } handleTypeAssignment(sb.context, right, left); sb.visit(tsUtils.expression.getLeft(expr), sb.setValueOptions(options)); } visitExpressionOperator(sb, kind, expr, options) { this.visitExpressionOperatorBase(sb, tsUtils.expression.getOperatorToken(expr), kind, tsUtils.expression.getLeft(expr), tsUtils.expression.getRight(expr), options); } visitLogicalExpressionOperator(sb, kind, expr, options) { this.visitLogicalExpressionOperatorBase(sb, tsUtils.expression.getOperatorToken(expr), kind, tsUtils.expression.getLeft(expr), tsUtils.expression.getRight(expr), options); } visitExpressionOperatorBase(sb, node, kind, left, right, optionsIn) { const options = sb.pushValueOptions(optionsIn); const leftType = sb.context.analysis.getType(left); const rightType = sb.context.analysis.getType(right); const visit = (leftHelper, rightHelper) => { sb.visit(left, options); sb.emitHelper(left, options, leftHelper({ type: leftType })); sb.visit(right, options); sb.emitHelper(right, options, (rightHelper === undefined ? leftHelper : rightHelper)({ type: rightType })); }; const visitNumeric = () => visit(sb.helpers.toNumber); const isBinaryNumeric = leftType !== undefined && tsUtils.type_.isOnlyNumberish(leftType) && rightType !== undefined && tsUtils.type_.isOnlyNumberish(rightType); switch (kind) { case ts.SyntaxKind.AsteriskToken: visitNumeric(); sb.emitOp(node, 'MUL'); sb.emitHelper(node, options, sb.helpers.wrapNumber); break; case ts.SyntaxKind.SlashToken: visitNumeric(); sb.emitOp(node, 'DIV'); sb.emitHelper(node, options, sb.helpers.wrapNumber); break; case ts.SyntaxKind.PercentToken: visitNumeric(); sb.emitOp(node, 'MOD'); sb.emitHelper(node, options, sb.helpers.wrapNumber); break; case ts.SyntaxKind.PlusToken: if (isBinaryNumeric) { visitNumeric(); sb.emitOp(node, 'ADD'); sb.emitHelper(node, options, sb.helpers.wrapNumber); } else if (leftType !== undefined && tsUtils.type_.isOnlyStringish(leftType) && rightType !== undefined && tsUtils.type_.isOnlyStringish(rightType)) { visit(() => sb.helpers.unwrapString); sb.emitOp(node, 'CAT'); sb.emitHelper(node, options, sb.helpers.wrapString); } else if (leftType !== undefined && tsUtils.type_.isOnlyStringish(leftType)) { visit(() => sb.helpers.unwrapString, sb.helpers.toString); sb.emitOp(node, 'CAT'); sb.emitHelper(node, options, sb.helpers.wrapString); } else if (rightType !== undefined && tsUtils.type_.isOnlyStringish(rightType)) { visit(sb.helpers.toString, () => sb.helpers.unwrapString); sb.emitOp(node, 'CAT'); sb.emitHelper(node, options, sb.helpers.wrapString); } else { visit(sb.helpers.toPrimitive); sb.emitOp(node, 'SWAP'); sb.emitOp(node, 'TUCK'); sb.emitOp(node, 'OVER'); sb.emitHelper(node, options, sb.helpers.isString); sb.emitOp(node, 'SWAP'); sb.emitHelper(node, options, sb.helpers.isString); sb.emitHelper(node, options, sb.helpers.if({ condition: () => { sb.emitOp(node, 'BOOLOR'); }, whenTrue: () => { sb.emitHelper(node, options, sb.helpers.toString({ type: sb.context.analysis.getType(right) })); sb.emitOp(node, 'SWAP'); sb.emitHelper(node, options, sb.helpers.toString({ type: sb.context.analysis.getType(left) })); sb.emitOp(node, 'SWAP'); sb.emitOp(node, 'CAT'); sb.emitHelper(node, options, sb.helpers.wrapString); }, whenFalse: () => { sb.emitHelper(node, options, sb.helpers.toNumber({ type: sb.context.analysis.getType(right) })); sb.emitOp(node, 'SWAP'); sb.emitHelper(node, options, sb.helpers.toNumber({ type: sb.context.analysis.getType(left) })); sb.emitOp(node, 'SWAP'); sb.emitOp(node, 'ADD'); sb.emitHelper(node, options, sb.helpers.wrapNumber); }, })); } break; case ts.SyntaxKind.MinusToken: visitNumeric(); sb.emitOp(node, 'SUB'); sb.emitHelper(node, options, sb.helpers.wrapNumber); break; case ts.SyntaxKind.GreaterThanGreaterThanToken: visitNumeric(); sb.emitOp(node, 'SHR'); sb.emitHelper(node, options, sb.helpers.wrapNumber); break; case ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken: visitNumeric(); sb.emitOp(node, 'SHR'); sb.emitHelper(node, options, sb.helpers.wrapNumber); break; case ts.SyntaxKind.LessThanLessThanToken: visitNumeric(); sb.emitOp(node, 'SHL'); sb.emitHelper(node, options, sb.helpers.wrapNumber); break; case ts.SyntaxKind.LessThanToken: sb.emitHelper(node, options, sb.helpers.lessThan({ leftFirst: true, left, right })); sb.emitHelper(node, options, sb.helpers.wrapBoolean); break; case ts.SyntaxKind.LessThanEqualsToken: sb.emitHelper(node, options, sb.helpers.lessThan({ leftFirst: false, left: right, right: left })); sb.emitOp(node, 'NOT'); sb.emitHelper(node, options, sb.helpers.wrapBoolean); break; case ts.SyntaxKind.GreaterThanToken: sb.emitHelper(node, options, sb.helpers.lessThan({ leftFirst: false, left: right, right: left })); sb.emitHelper(node, options, sb.helpers.wrapBoolean); break; case ts.SyntaxKind.GreaterThanEqualsToken: sb.emitHelper(node, options, sb.helpers.lessThan({ leftFirst: true, left, right })); sb.emitOp(node, 'NOT'); sb.emitHelper(node, options, sb.helpers.wrapBoolean); break; case ts.SyntaxKind.ExclamationEqualsToken: sb.emitHelper(node, options, sb.helpers.equalsEquals({ left, right })); sb.emitOp(node, 'NOT'); sb.emitHelper(node, options, sb.helpers.wrapBoolean); break; case ts.SyntaxKind.EqualsEqualsToken: sb.emitHelper(node, options, sb.helpers.equalsEquals({ left, right })); sb.emitHelper(node, options, sb.helpers.wrapBoolean); break; case ts.SyntaxKind.AmpersandToken: visitNumeric(); sb.emitOp(node, 'AND'); sb.emitHelper(node, options, sb.helpers.wrapNumber); break; case ts.SyntaxKind.BarToken: visitNumeric(); sb.emitOp(node, 'OR'); sb.emitHelper(node, options, sb.helpers.wrapNumber); break; case ts.SyntaxKind.CaretToken: visitNumeric(); sb.emitOp(node, 'XOR'); sb.emitHelper(node, options, sb.helpers.wrapNumber); break; case ts.SyntaxKind.InKeyword: sb.visit(right, options); sb.visit(left, options); sb.emitHelper(node, options, sb.helpers.inObjectProperty({ propType: leftType })); sb.emitHelper(node, options, sb.helpers.wrapBoolean); break; case ts.SyntaxKind.InstanceOfKeyword: this.handleInstanceOf(sb, node, left, right, options); break; case ts.SyntaxKind.CommaToken: sb.visit(left, options); sb.emitOp(node, 'DROP'); sb.visit(right, options); break; case ts.SyntaxKind.AsteriskAsteriskToken: visitNumeric(); sb.emitHelper(node, options, sb.helpers.exp); sb.emitHelper(node, options, sb.helpers.wrapNumber); break; case ts.SyntaxKind.EqualsEqualsEqualsToken: sb.visit(left, options); sb.visit(right, options); sb.emitHelper(node, options, sb.helpers.equalsEqualsEquals({ leftType, rightType })); sb.emitHelper(node, options, sb.helpers.wrapBoolean); break; case ts.SyntaxKind.ExclamationEqualsEqualsToken: sb.visit(left, options); sb.visit(right, options); sb.emitHelper(node, options, sb.helpers.equalsEqualsEquals({ leftType, rightType })); sb.emitOp(node, 'NOT'); sb.emitHelper(node, options, sb.helpers.wrapBoolean); break; default: utils.assertNever(kind); } if (!optionsIn.pushValue) { sb.emitOp(node, 'DROP'); return; } } visitLogicalExpressionOperatorBase(sb, node, kind, left, right, options) { switch (kind) { case ts.SyntaxKind.AmpersandAmpersandToken: { sb.emitHelper(node, options, sb.helpers.if({ condition: () => { sb.visit(left, sb.pushValueOptions(options)); if (options.pushValue) { sb.emitOp(left, 'DUP'); } sb.emitHelper(left, sb.pushValueOptions(options), sb.helpers.toBoolean({ type: sb.context.analysis.getType(left) })); }, whenTrue: () => { if (options.pushValue) { sb.emitOp(node, 'DROP'); } sb.visit(right, options); }, })); break; } case ts.SyntaxKind.BarBarToken: { sb.emitHelper(node, options, sb.helpers.if({ condition: () => { sb.visit(left, sb.pushValueOptions(options)); if (options.pushValue) { sb.emitOp(left, 'DUP'); } sb.emitHelper(left, sb.pushValueOptions(options), sb.helpers.toBoolean({ type: sb.context.analysis.getType(left) })); }, whenFalse: () => { if (options.pushValue) { sb.emitOp(node, 'DROP'); } sb.visit(right, options); }, })); break; } case ts.SyntaxKind.QuestionQuestionToken: { sb.emitHelper(node, options, sb.helpers.if({ condition: () => { sb.visit(left, sb.pushValueOptions(options)); if (options.pushValue) { sb.emitOp(left, 'DUP'); } sb.emitHelper(left, sb.pushValueOptions(options), sb.helpers.toNullishBoolean({ type: sb.context.analysis.getType(left) })); }, whenFalse: () => { if (options.pushValue) { sb.emitOp(node, 'DROP'); } sb.visit(right, options); }, whenTrue: () => { if (options.pushValue) { sb.emitOp(node, 'DROP'); } sb.visit(left, options); }, })); break; } default: utils.assertNever(kind); } } handleInstanceOf(sb, node, left, right, options) { const builtin = sb.context.builtins.getValue(right); if (builtin !== undefined && isBuiltinInstanceOf(builtin)) { builtin.emitInstanceOf(sb, left, options); return; } sb.visit(left, options); sb.visit(right, options); sb.emitHelper(node, options, sb.helpers.instanceof); sb.emitHelper(node, options, sb.helpers.wrapBoolean); } } //# sourceMappingURL=BinaryExpressionCompiler.js.map