js-slang
Version:
Javascript-based implementations of Source, written in Typescript
506 lines • 17.1 kB
JavaScript
"use strict";
/*
* Translate our AST to estree AST (Source's AST)
* */
Object.defineProperty(exports, "__esModule", { value: true });
exports.Translator = void 0;
const tokens_1 = require("./tokens");
const errors_1 = require("./errors");
class Translator {
constructor(source) {
this.source = source;
}
tokenToEstreeLocation(token) {
// Convert zero-based to one-based.
const line = token.line + 1;
const start = {
line,
column: token.col - token.lexeme.length
};
const end = {
line,
column: token.col
};
const source = token.lexeme;
return { source, start, end };
}
toEstreeLocation(stmt) {
const start = {
// Convert zero-based to one-based.
line: stmt.startToken.line + 1,
column: stmt.startToken.col - stmt.startToken.lexeme.length
};
const end = {
// Convert zero-based to one-based.
line: stmt.endToken.line + 1,
column: stmt.endToken.col
};
const source = this.source.slice(stmt.startToken.indexInSource, stmt.endToken.indexInSource + stmt.endToken.lexeme.length);
return { source, start, end };
}
resolve(stmt) {
return stmt.accept(this);
}
// Ugly, but just to support proper typing
resolveStmt(stmt) {
return stmt.accept(this);
}
resolveManyStmt(stmts) {
const res = [];
for (const stmt of stmts) {
res.push(this.resolveStmt(stmt));
}
return res;
}
resolveExpr(expr) {
return expr.accept(this);
}
resolveManyExpr(exprs) {
const res = [];
for (const expr of exprs) {
res.push(this.resolveExpr(expr));
}
return res;
}
// Converts our internal identifier to estree identifier.
rawStringToIdentifier(name, stmtOrExpr) {
const keywords = new Set(['abstract', 'arguments', 'await', 'boolean', 'byte',
'case', 'catch', 'char', 'const', 'debugger', 'default', 'delete', 'do', 'double', 'enum',
'eval', 'export', 'extends', 'false', 'final', 'float', 'function', 'goto', 'implements',
'instanceof', 'int', 'interface', 'let', 'long', 'native', 'new', 'null', 'package',
'private', 'protected', 'public', 'short', 'static', 'super', 'switch', 'synchronized', 'this',
'throw', 'throws', 'transient', 'true', 'typeof', 'var', 'void', 'volatile']);
return {
type: 'Identifier',
name: keywords.has(name) ? '$' + name : name,
loc: this.toEstreeLocation(stmtOrExpr),
};
}
// Token to estree identifier.
convertToIdentifier(name) {
const keywords = new Set(['abstract', 'arguments', 'await', 'boolean', 'byte',
'case', 'catch', 'char', 'const', 'debugger', 'default', 'delete', 'do', 'double', 'enum',
'eval', 'export', 'extends', 'false', 'final', 'float', 'function', 'goto', 'implements',
'instanceof', 'int', 'interface', 'let', 'long', 'native', 'new', 'null', 'package',
'private', 'protected', 'public', 'short', 'static', 'super', 'switch', 'synchronized', 'this',
'throw', 'throws', 'transient', 'true', 'typeof', 'var', 'void', 'volatile']);
return {
type: 'Identifier',
name: keywords.has(name.lexeme) ? '$' + name.lexeme : name.lexeme,
loc: this.tokenToEstreeLocation(name),
};
}
convertToIdentifiers(names) {
return names.map(name => this.convertToIdentifier(name));
}
// private convertToExpressionStatement(expr: Expression): ExpressionStatement {
// return {
// type: 'ExpressionStatement',
// expression: expr,
// // loc: this.toEstreeLocation(),
// }
// }
// private converTokenstoDecls(varDecls: Token[]): VariableDeclaration {
// return {
// type: 'VariableDeclaration',
// declarations: varDecls?.map((token): VariableDeclarator => {
// return {
// type: 'VariableDeclarator',
// id: this.convertToIdentifier(token),
// loc: this.tokenToEstreeLocation(token),
// }
// }),
// kind: 'var',
// loc: this.toEstreeLocation(),
// };
// }
// Wraps an array of statements to a block.
// WARNING: THIS CREATES A NEW BLOCK IN
// JS AST. THIS ALSO MEANS A NEW NAMESPACE. BE CAREFUL!
wrapInBlock(stmt, stmts) {
return {
type: 'BlockStatement',
body: this.resolveManyStmt(stmts),
loc: this.toEstreeLocation(stmt),
};
}
//// STATEMENTS
visitFileInputStmt(stmt) {
const newBody = this.resolveManyStmt(stmt.statements);
// if (stmt.varDecls !== null && stmt.varDecls.length > 0) {
// const decls = this.converTokenstoDecls(stmt.varDecls);
// newBody.unshift(decls);
// }
return {
type: 'Program',
sourceType: 'module',
body: newBody,
loc: this.toEstreeLocation(stmt),
};
}
visitIndentCreation(stmt) {
return {
type: 'EmptyStatement',
loc: this.toEstreeLocation(stmt),
};
}
visitDedentCreation(stmt) {
return {
type: 'EmptyStatement',
loc: this.toEstreeLocation(stmt),
};
}
visitFunctionDefStmt(stmt) {
const newBody = this.resolveManyStmt(stmt.body);
// if (stmt.varDecls !== null && stmt.varDecls.length > 0) {
// const decls = this.converTokenstoDecls(stmt.varDecls);
// newBody.unshift(decls);
// }
return {
type: 'FunctionDeclaration',
id: this.convertToIdentifier(stmt.name),
params: this.convertToIdentifiers(stmt.parameters),
body: {
type: 'BlockStatement',
body: newBody,
},
loc: this.toEstreeLocation(stmt),
};
}
visitAnnAssignStmt(stmt) {
return {
type: 'AssignmentExpression',
// We only have one type of assignment in restricted Python.
operator: '=',
left: this.convertToIdentifier(stmt.name),
right: this.resolveExpr(stmt.value),
loc: this.toEstreeLocation(stmt),
};
}
// Note: assignments are expressions in JS.
visitAssignStmt(stmt) {
// return this.convertToExpressionStatement({
// type: 'AssignmentExpression',
// // We only have one type of assignment in restricted Python.
// operator: '=',
// left: this.convertToIdentifier(stmt.name),
// right: this.resolveExpr(stmt.value),
// loc: this.toEstreeLocation(stmt),
// })
const declaration = {
type: 'VariableDeclarator',
id: this.convertToIdentifier(stmt.name),
loc: this.tokenToEstreeLocation(stmt.name),
init: this.resolveExpr(stmt.value),
};
return {
type: 'VariableDeclaration',
declarations: [declaration],
// Note: we abuse the fact that var is function and module scoped
// which is exactly the same as how Python assignments are scoped!
kind: 'var',
loc: this.toEstreeLocation(stmt),
};
}
// Convert to source's built-in assert function.
visitAssertStmt(stmt) {
return {
type: 'CallExpression',
optional: false,
callee: this.rawStringToIdentifier('assert', stmt),
arguments: [this.resolveExpr(stmt.value)],
// @TODO, this needs to come after callee
loc: this.toEstreeLocation(stmt),
};
}
// @TODO decide how to do for loops
// For now, empty block
visitForStmt(stmt) {
return {
type: 'EmptyStatement',
loc: this.toEstreeLocation(stmt),
};
}
visitIfStmt(stmt) {
return {
type: 'IfStatement',
test: this.resolveExpr(stmt.condition),
consequent: this.wrapInBlock(stmt, stmt.body),
alternate: stmt.elseBlock !== null ? this.wrapInBlock(stmt, stmt.elseBlock) : null,
loc: this.toEstreeLocation(stmt),
};
}
visitGlobalStmt(stmt) {
return {
type: 'EmptyStatement',
loc: this.toEstreeLocation(stmt),
};
}
visitNonLocalStmt(stmt) {
return {
type: 'EmptyStatement',
loc: this.toEstreeLocation(stmt),
};
}
visitReturnStmt(stmt) {
return {
type: 'ReturnStatement',
argument: stmt.value == null ? null : this.resolveExpr(stmt.value),
loc: this.toEstreeLocation(stmt),
};
}
visitWhileStmt(stmt) {
return {
type: 'WhileStatement',
test: this.resolveExpr(stmt.condition),
body: this.wrapInBlock(stmt, stmt.body),
loc: this.toEstreeLocation(stmt),
};
}
visitSimpleExprStmt(stmt) {
return {
type: 'ExpressionStatement',
expression: this.resolveExpr(stmt.expression),
loc: this.toEstreeLocation(stmt),
};
}
// @TODO
visitFromImportStmt(stmt) {
const specifiers = stmt.names.map(name => {
const ident = this.convertToIdentifier(name);
return {
type: 'ImportSpecifier',
imported: ident,
local: ident,
};
});
return {
type: 'ImportDeclaration',
specifiers: specifiers,
source: {
type: 'Literal',
value: stmt.module.lexeme,
loc: this.tokenToEstreeLocation(stmt.module)
}
};
}
visitContinueStmt(stmt) {
return {
type: 'ContinueStatement',
loc: this.toEstreeLocation(stmt),
};
}
visitBreakStmt(stmt) {
return {
type: 'BreakStatement',
loc: this.toEstreeLocation(stmt),
};
}
visitPassStmt(stmt) {
return {
type: 'EmptyStatement',
loc: this.toEstreeLocation(stmt),
};
}
//// EXPRESSIONS
visitVariableExpr(expr) {
return this.convertToIdentifier(expr.name);
}
visitLambdaExpr(expr) {
return {
type: 'ArrowFunctionExpression',
expression: true,
params: this.convertToIdentifiers(expr.parameters),
body: this.resolveExpr(expr.body),
loc: this.toEstreeLocation(expr),
};
}
// disabled for now
visitMultiLambdaExpr(expr) {
return {
type: 'EmptyStatement',
loc: this.toEstreeLocation(expr),
};
}
visitUnaryExpr(expr) {
const op = expr.operator.type;
let res = '-';
let plus = false;
switch (op) {
case tokens_1.TokenType.NOT:
res = '!';
break;
case tokens_1.TokenType.PLUS:
res = '+';
plus = true;
break;
case tokens_1.TokenType.MINUS:
res = '-';
break;
default:
throw new Error("Unreachable code path in translator");
}
if (plus) {
return {
type: 'CallExpression',
optional: false,
callee: {
type: 'Identifier',
name: '__py_unary_plus',
loc: this.toEstreeLocation(expr),
},
arguments: [this.resolveExpr(expr.right)],
loc: this.toEstreeLocation(expr),
};
}
return {
type: 'UnaryExpression',
// To satisfy the type checker.
operator: res,
prefix: true,
argument: this.resolveExpr(expr.right),
loc: this.toEstreeLocation(expr),
};
}
visitGroupingExpr(expr) {
return this.resolveExpr(expr.expression);
}
visitBinaryExpr(expr) {
const op = expr.operator.type;
let res = '';
// To make the type checker happy.
switch (op) {
case tokens_1.TokenType.PLUS:
res = '__py_adder';
break;
case tokens_1.TokenType.MINUS:
res = '__py_minuser';
break;
case tokens_1.TokenType.STAR:
res = '__py_multiplier';
break;
case tokens_1.TokenType.SLASH:
res = '__py_divider';
break;
case tokens_1.TokenType.PERCENT:
res = '__py_modder';
break;
// @TODO double slash and power needs to convert to math exponent/floor divide
case tokens_1.TokenType.DOUBLESLASH:
res = '__py_floorer';
break;
case tokens_1.TokenType.DOUBLESTAR:
res = '__py_powerer';
break;
default:
throw new Error("Unreachable binary code path in translator");
}
return {
type: 'CallExpression',
optional: false,
callee: {
type: 'Identifier',
name: res,
loc: this.toEstreeLocation(expr),
},
arguments: [this.resolveExpr(expr.left), this.resolveExpr(expr.right)],
loc: this.toEstreeLocation(expr),
};
}
visitCompareExpr(expr) {
const op = expr.operator.type;
let res = '+';
// To make the type checker happy.
switch (op) {
case tokens_1.TokenType.LESS:
res = '<';
break;
case tokens_1.TokenType.GREATER:
res = '>';
break;
case tokens_1.TokenType.DOUBLEEQUAL:
res = '===';
break;
case tokens_1.TokenType.GREATEREQUAL:
res = '>=';
break;
case tokens_1.TokenType.LESSEQUAL:
res = '<=';
break;
case tokens_1.TokenType.NOTEQUAL:
res = '!==';
break;
// @TODO we need to convert these to builtin function applications.
case tokens_1.TokenType.IS:
case tokens_1.TokenType.ISNOT:
case tokens_1.TokenType.IN:
case tokens_1.TokenType.NOTIN:
throw new errors_1.TranslatorErrors.UnsupportedOperator(expr.operator.line, expr.operator.col, this.source, expr.operator.indexInSource);
default:
throw new Error("Unreachable binary code path in translator");
}
return {
type: 'BinaryExpression',
operator: res,
left: this.resolveExpr(expr.left),
right: this.resolveExpr(expr.right),
loc: this.toEstreeLocation(expr),
};
}
visitBoolOpExpr(expr) {
const op = expr.operator.type;
let res = '||';
// To make the type checker happy.
switch (op) {
case tokens_1.TokenType.AND:
res = '&&';
break;
case tokens_1.TokenType.OR:
res = '||';
break;
default:
throw new Error("Unreachable binary code path in translator");
}
return {
type: 'LogicalExpression',
operator: res,
left: this.resolveExpr(expr.left),
right: this.resolveExpr(expr.right),
loc: this.toEstreeLocation(expr),
};
}
visitCallExpr(expr) {
return {
type: 'CallExpression',
optional: false,
callee: this.resolveExpr(expr.callee),
arguments: this.resolveManyExpr(expr.args),
loc: this.toEstreeLocation(expr),
};
}
visitTernaryExpr(expr) {
return {
type: 'ConditionalExpression',
test: this.resolveExpr(expr.predicate),
alternate: this.resolveExpr(expr.alternative),
consequent: this.resolveExpr(expr.consequent),
loc: this.toEstreeLocation(expr),
};
}
visitLiteralExpr(expr) {
return {
type: 'Literal',
value: expr.value,
loc: this.toEstreeLocation(expr),
};
}
visitBigIntLiteralExpr(expr) {
return {
type: 'Literal',
bigint: expr.value,
loc: this.toEstreeLocation(expr),
};
}
}
exports.Translator = Translator;
//# sourceMappingURL=translator.js.map