UNPKG

tstosc

Version:

A transpiler that convert TypeScript to SuperCollider's SCLang.

400 lines (397 loc) 19.3 kB
import ts from 'typescript'; import { isTSLiteral, convertTSLiteralToSC, convertToSCSymbol } from './literal_conv.js'; import { UnsupportedTypeError, RuntimeError } from '../../../util/error.js'; import { default_generator_context } from '../context.js'; const supported_expression_syntax_kind = [ ts.SyntaxKind.ArrayLiteralExpression, // Considered and processed as literal. ts.SyntaxKind.ObjectLiteralExpression, // Considered and processed as literal. ts.SyntaxKind.PropertyAccessExpression, ts.SyntaxKind.ElementAccessExpression, ts.SyntaxKind.CallExpression, ts.SyntaxKind.NewExpression, ts.SyntaxKind.TaggedTemplateExpression, ts.SyntaxKind.TypeAssertionExpression, ts.SyntaxKind.ParenthesizedExpression, ts.SyntaxKind.FunctionExpression, // Considered and processed as literal. ts.SyntaxKind.ArrowFunction, // Considered and processed as literal. // ts.SyntaxKind.DeleteExpression, // Low impl possibility. // ts.SyntaxKind.TypeOfExpression, // TODO. // ts.SyntaxKind.VoidExpression, // Meaningless when writing program for SuperCollider. // ts.SyntaxKind.AwaitExpression, // Might be impl. ts.SyntaxKind.PrefixUnaryExpression, ts.SyntaxKind.PostfixUnaryExpression, ts.SyntaxKind.BinaryExpression, ts.SyntaxKind.ConditionalExpression, ts.SyntaxKind.TemplateExpression, // Considered and processed as literal. // ts.SyntaxKind.YieldExpression, // Might be supported in the future. ts.SyntaxKind.SpreadElement, ts.SyntaxKind.ClassExpression, ts.SyntaxKind.OmittedExpression, ts.SyntaxKind.ExpressionWithTypeArguments, ts.SyntaxKind.AsExpression, ts.SyntaxKind.NonNullExpression, ts.SyntaxKind.MetaProperty, // ts.SyntaxKind.SyntheticExpression, // Internal, not possible to be seen in AST. ts.SyntaxKind.SatisfiesExpression ]; function convertTSExpressionToSC(expr, generator_context = default_generator_context) { if (isTSLiteral(expr)) { return convertTSLiteralToSC(expr, generator_context); } if (ts.isIdentifier(expr)) { return (generator_context.is_generating_class_name ? escapeForSCClassIfNeeded : escapeForSCVarIfNeeded)(expr.text); } if (generator_context.has_unhandled_self_indecr_operator) { const expr_to_wrap = tryFindSelfIndecrOperator(expr); if (expr_to_wrap.length > 0) { let getName2 = function(e) { const id = e.getText().replace(/[^\w]/g, "_"); return "~tstosc__temp__" + (id.length == 0 ? count++ : id); }; let count = 0; const expr_and_names = expr_to_wrap.map((e) => [e, getName2(e)]); const name_of_expr = `~tstosc__temp_result__${count++}`; expr_and_names.forEach(([e, n]) => generator_context.expression_temporise_dict.set(e, n)); const result = [ // This generate things like `~tstosc__temp_a = Ref.new(a) ;`. expr_and_names.map(([e, n]) => `${n} = \`(${convertTSExpressionToSC(e, generator_context)}) ;`).deduplicated().join("\n"), name_of_expr + " = " + convertTSExpressionToSC(expr, { ...generator_context, has_unhandled_self_indecr_operator: false }) + " ;", expr_and_names.map(([e, n]) => `${convertTSExpressionToSC(e, generator_context)} = ${n}.value ;`).deduplicated().join("\n"), name_of_expr ].join("\n"); expr_and_names.forEach(([e, _]) => generator_context.expression_temporise_dict.delete(e)); return result; } } switch (expr.kind) { case ts.SyntaxKind.PropertyAccessExpression: return convertTSPropertyAccessExpressionToSC(expr, generator_context); case ts.SyntaxKind.ElementAccessExpression: return convertTSElementAccessExpressionToSC(expr, generator_context); case ts.SyntaxKind.CallExpression: return convertTSCallExpressionToSC(expr, generator_context); case ts.SyntaxKind.NewExpression: return convertTSNewExpressionToSC(expr, generator_context); case ts.SyntaxKind.TaggedTemplateExpression: return convertTSTaggedTemplateExpressionToSC(expr, generator_context); case ts.SyntaxKind.ParenthesizedExpression: return "(" + convertTSExpressionToSC(expr.expression, generator_context) + ")"; case ts.SyntaxKind.PrefixUnaryExpression: return translateTSPrefixUnaryExpressionToSC(expr, generator_context); case ts.SyntaxKind.PostfixUnaryExpression: return translateTSPostfixUnaryExpressionToSC(expr, generator_context); case ts.SyntaxKind.BinaryExpression: return convertTSBinaryExpressionToSC(expr, generator_context); case ts.SyntaxKind.ConditionalExpression: return convertTSConditionalExpressionToSC(expr, generator_context); case ts.SyntaxKind.ThisKeyword: { switch (generator_context.this_coming_from) { case "object_literal_parameter": return "tstosc__this_param"; case "built_instance": return "tstosc__built_instance"; case "itself": return "this"; case "nothing": default: throw TypeError("`this` here is not associated with outside."); } } case ts.SyntaxKind.SuperKeyword: { switch (generator_context.super_means) { // If in a constructor, the first super call should be turn into `super.new`. case "constructor": return "super.new"; // If in a static method, calling `super` is equal to use super class's name. case "class_name": return generator_context.class_info.super_class_name; // If it means same as `this` (in this condition, `super` is interchangable with `this`). case "as_it_is": return "super"; case "nothing": default: throw TypeError("`super` here is not associated with a class method/constructor."); } } case ts.SyntaxKind.ClassExpression: // The `...` in `[1, 2, ...[3, 4]]`. case ts.SyntaxKind.SpreadElement: // Handled in array transforming. // The missing part like `let [a0, , a2] = [0, 1, 2]`, the place that should be `a1`. case ts.SyntaxKind.OmittedExpression: return ""; // Since sclang does not have static-typing, ignore these statement. case ts.SyntaxKind.TypeAssertionExpression: case ts.SyntaxKind.ExpressionWithTypeArguments: // Like `<string>` in `Array<string>`. case ts.SyntaxKind.AsExpression: case ts.SyntaxKind.NonNullExpression: case ts.SyntaxKind.SatisfiesExpression: return convertTSExpressionToSC(expr.expression, generator_context); case ts.SyntaxKind.TypeOfExpression: // TODO: Will be implemented soon. case ts.SyntaxKind.AwaitExpression: // Might be supported in the future. case ts.SyntaxKind.YieldExpression: // Might be supported in the future. case ts.SyntaxKind.MetaProperty: // Low possibility to be implemented... case ts.SyntaxKind.DeleteExpression: // Low possibility to be implemented... default: throw UnsupportedTypeError.forNodeWithSyntaxKind(expr, "expression"); } } function isSelfIndecrExpression(e) { return (ts.isPrefixUnaryExpression(e) || ts.isPostfixUnaryExpression(e)) && (e.operator == ts.SyntaxKind.PlusPlusToken || e.operator == ts.SyntaxKind.MinusMinusToken); } function hasSelfIndecrExpression(e) { if (ts.isExpression(e) && isSelfIndecrExpression(e)) { return true; } else { return e.forEachChild(hasSelfIndecrExpression) ?? false; } } function tryFindSelfIndecrOperator(e) { let result = []; function traverse(node) { if (ts.isExpression(node) && isSelfIndecrExpression(node)) { result.push( ts.isParenthesizedExpression(node.operand) ? node.operand.expression : node.operand ); } node.forEachChild(traverse); } traverse(e); return result; } function isStoringObjectLiteral(node, generator_context) { if (node.kind == ts.SyntaxKind.ThisKeyword || node.kind == ts.SyntaxKind.SuperKeyword) { return false; } if (ts.isIdentifier(node) && node.text.startsWith("tstosc_dvar_")) { return true; } const node_type = generator_context.compiler_program.getTypeChecker().getTypeAtLocation(node); return "symbol" in node_type && (node_type.symbol.escapedName == "__object" || (node_type.symbol.flags & ts.SymbolFlags.ObjectLiteral) != 0); } function convertTSPropertyAccessExpressionToSC(e, generator_context = default_generator_context) { return isStoringObjectLiteral(e.expression, generator_context) || generator_context.this_coming_from == "object_literal_parameter" ? `${convertTSExpressionToSC(e.expression, generator_context)}["${escapeForSCVarIfNeeded(e.name.text)}"]` : `${convertTSExpressionToSC(e.expression, generator_context)}.${escapeForSCVarIfNeeded(e.name.text)}`; } function convertTSElementAccessExpressionToSC(e, generator_context = default_generator_context) { return convertTSExpressionToSC(e.expression, generator_context) + "[" + convertTSExpressionToSC(e.argumentExpression, generator_context) + "]"; } function isMethod(node, generator_context) { const node_type = generator_context.compiler_program.getTypeChecker().getTypeAtLocation(node); return "symbol" in node_type && (node_type.symbol.flags & ts.SymbolFlags.Method) != 0; } function convertTSCallExpressionToSC(e, generator_context = default_generator_context) { const is_left_side_a_constructor_super = e.expression.kind == ts.SyntaxKind.SuperKeyword && generator_context.is_generating_constructor; const left_side = is_left_side_a_constructor_super ? convertTSExpressionToSC(e.expression, generator_context.makeSuperMeans("constructor")) : convertTSExpressionToSC(e.expression, generator_context); const dot_or_not = ts.isPropertyAccessExpression(e.expression) || isMethod(e.expression, generator_context) || is_left_side_a_constructor_super ? "" : "."; const result = left_side + dot_or_not + "(" + e.arguments.map((a) => convertTSExpressionToSC(a, generator_context)).join(", ") + ")"; return is_left_side_a_constructor_super ? "(tstosc__built_instance = " + result + ")" : result; } function convertTSNewExpressionToSC(e, generator_context = default_generator_context) { return convertTSExpressionToSC(e.expression, generator_context.willGenerateClassName()) + ".new(" + (e.arguments?.map((a) => convertTSExpressionToSC(a, generator_context)).join(", ") ?? "") + ")"; } function convertTSTaggedTemplateExpressionToSC(e, generator_context = default_generator_context) { const { template, tag } = e; if (ts.isNoSubstitutionTemplateLiteral(template)) { return convertTSCallExpressionToSC(ts.factory.createCallExpression( tag, /* type_arguments */ void 0, [ // The tag-function's first argument is the plain-text in the template. ts.factory.createArrayLiteralExpression([ts.factory.createStringLiteral(template.text)]) ] ), generator_context); } else { return convertTSCallExpressionToSC(ts.factory.createCallExpression( tag, /* type_arguments */ void 0, [ // The tag-function's first argument is the plain-text in the template. ts.factory.createArrayLiteralExpression([ template.head, ...template.templateSpans.map((s) => s.literal) ].map((s) => ts.factory.createStringLiteral(s.text))), // The following argument is the template's `${}` expression part. ...template.templateSpans.map((s) => s.expression) ] ), generator_context); } } function returnOrThrowIfNotInTemporiseDict(e, generator_context) { const temp_name = generator_context.expression_temporise_dict.get(e); if (temp_name == void 0) { throw new RuntimeError( `Expression ("${e.getText()}") should be in expression_temporise_dict, but not found.` ); } return temp_name; } function translateTSPrefixUnaryExpressionToSC(e, generator_context = default_generator_context) { switch (e.operator) { case ts.SyntaxKind.MinusToken: return "-" + convertTSExpressionToSC(e.operand, generator_context); case ts.SyntaxKind.ExclamationToken: return "not(" + convertTSExpressionToSC(e.operand, generator_context) + ")"; case ts.SyntaxKind.PlusPlusToken: return "~tstosc__pre_incr.(" + returnOrThrowIfNotInTemporiseDict(e.operand, generator_context) + ")"; case ts.SyntaxKind.MinusMinusToken: return "~tstosc__pre_decr.(" + returnOrThrowIfNotInTemporiseDict(e.operand, generator_context) + ")"; case ts.SyntaxKind.TildeToken: return "bitNot(" + convertTSExpressionToSC(e.operand, generator_context) + ")"; case ts.SyntaxKind.PlusToken: return convertTSExpressionToSC(e.operand, generator_context); default: throw UnsupportedTypeError.ofSyntaxKind( e.operator, "unary prefix operator", e ); } } function translateTSPostfixUnaryExpressionToSC(e, generator_context = default_generator_context) { switch (e.operator) { case ts.SyntaxKind.PlusPlusToken: return "~tstosc__post_incr.(" + returnOrThrowIfNotInTemporiseDict(e.operand, generator_context) + ")"; case ts.SyntaxKind.MinusMinusToken: return "~tstosc__post_decr.(" + returnOrThrowIfNotInTemporiseDict(e.operand, generator_context) + ")"; default: throw UnsupportedTypeError.ofSyntaxKind( e.operator, "unary postfix operator", e ); } } function convertTSConditionalExpressionToSC(e, generator_context = default_generator_context) { return "if(" + convertTSExpressionToSC(e.condition, generator_context.atValuePosition()) + ", " + convertTSExpressionToSC(e.whenTrue, generator_context.atValuePosition()) + ", " + convertTSExpressionToSC(e.whenFalse, generator_context.atValuePosition()) + ")"; } const ts_infix_becoming_binary_operator_to_sc_dict = /* @__PURE__ */ new Map([ [ts.SyntaxKind.PlusToken, "+"], [ts.SyntaxKind.MinusToken, "-"], [ts.SyntaxKind.AsteriskToken, "*"], [ts.SyntaxKind.SlashToken, "/"], [ts.SyntaxKind.PercentToken, "%"], [ts.SyntaxKind.AsteriskAsteriskToken, "**"], [ts.SyntaxKind.EqualsToken, "="], [ts.SyntaxKind.EqualsEqualsToken, "=="], [ts.SyntaxKind.ExclamationEqualsToken, "!="], [ts.SyntaxKind.EqualsEqualsEqualsToken, "=="], [ts.SyntaxKind.LessThanToken, "<"], [ts.SyntaxKind.GreaterThanToken, ">"], [ts.SyntaxKind.LessThanEqualsToken, "<="], [ts.SyntaxKind.GreaterThanEqualsToken, ">="], [ts.SyntaxKind.AmpersandAmpersandToken, "&&"], [ts.SyntaxKind.BarBarToken, "||"], [ts.SyntaxKind.QuestionQuestionToken, "??"] ]); const ts_function_call_becoming_operator_to_sc_dict = /* @__PURE__ */ new Map([ // Unary [ts.SyntaxKind.TildeToken, "bitNot"], [ts.SyntaxKind.ExclamationToken, "not"], // Binary [ts.SyntaxKind.AmpersandToken, "bitAnd"], [ts.SyntaxKind.BarToken, "bitOr"], [ts.SyntaxKind.CaretToken, "bitXor"], [ts.SyntaxKind.LessThanLessThanToken, "leftShift"], [ts.SyntaxKind.GreaterThanGreaterThanToken, "rightShift"], [ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken, "unsignedRightShift"] ]); const ts_shorthand_assignment_operator_to_ts_operator_dict = /* @__PURE__ */ new Map([ // Arithmetic [ts.SyntaxKind.PlusEqualsToken, ts.SyntaxKind.PlusToken], [ts.SyntaxKind.MinusEqualsToken, ts.SyntaxKind.MinusToken], [ts.SyntaxKind.AsteriskEqualsToken, ts.SyntaxKind.AsteriskToken], [ts.SyntaxKind.SlashEqualsToken, ts.SyntaxKind.SlashToken], [ts.SyntaxKind.PercentEqualsToken, ts.SyntaxKind.PercentToken], [ts.SyntaxKind.AsteriskAsteriskEqualsToken, ts.SyntaxKind.AsteriskAsteriskToken], // Bitwise [ts.SyntaxKind.AmpersandEqualsToken, ts.SyntaxKind.AmpersandToken], [ts.SyntaxKind.BarEqualsToken, ts.SyntaxKind.BarToken], [ts.SyntaxKind.CaretEqualsToken, ts.SyntaxKind.CaretToken], [ts.SyntaxKind.LessThanLessThanEqualsToken, ts.SyntaxKind.LessThanLessThanToken], [ts.SyntaxKind.GreaterThanGreaterThanEqualsToken, ts.SyntaxKind.GreaterThanGreaterThanToken], [ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken, ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken] ]); function convertTSBinaryExpressionToSC(e, generator_context = default_generator_context) { const [left_expr, op_syntax_kind, right_expr] = [e.left, e.operatorToken.kind, e.right]; { const op_conved = ts_infix_becoming_binary_operator_to_sc_dict.get(op_syntax_kind); if (op_conved != void 0) { return convertTSExpressionToSC(e.left, generator_context.atValuePosition()) + " " + op_conved + " " + convertTSExpressionToSC(e.right, generator_context.atValuePosition()); } } { const op_conved = ts_function_call_becoming_operator_to_sc_dict.get(op_syntax_kind); if (op_conved != void 0) { return op_conved + "(" + convertTSExpressionToSC(left_expr, generator_context.atValuePosition()) + ", " + convertTSExpressionToSC(right_expr, generator_context.atValuePosition()) + ")"; } } { const ts_op_conved = ts_shorthand_assignment_operator_to_ts_operator_dict.get(op_syntax_kind); if (ts_op_conved != void 0) { return convertTSExpressionToSC(ts.factory.createAssignment( // Make convert such as `a += 1` to `a = a + 1`. left_expr, ts.factory.createBinaryExpression(left_expr, ts_op_conved, right_expr) ), generator_context); } } switch (op_syntax_kind) { case ts.SyntaxKind.InstanceOfKeyword: return convertTSExpressionToSC(left_expr, generator_context.atValuePosition()) + ".isKindOf(" + right_expr.text + ")"; case ts.SyntaxKind.InKeyword: return right_expr.text + ".respondsTo(" + convertToSCSymbol(left_expr) + ")"; default: throw UnsupportedTypeError.forNodeWithSyntaxKind(e.operatorToken, "operator"); } } function isLegalSCIdentifier(name) { return /^[A-Za-z]\w*$/g.test(name); } function isLegalSCVar(name) { return isLegalSCIdentifier(name) && "a" <= name[0] && name[0] <= "z"; } function isLegalSCClass(name) { return isLegalSCIdentifier(name) && "A" <= name[0] && name[0] <= "Z"; } function escapeForSCVarIfNeeded(name, esc_seq = "escvar_") { if (isLegalSCVar(name)) { return name; } else { const escaped_name = esc_seq + name.replace(/\W/g, "_"); console.warn( `The variable name "${name}" is illegal in SCLang, and will be replaced to "${escaped_name}".` ); return escaped_name; } } function escapeForSCFunctionIfNeeded(name, esc_seq = "escfunction_") { return escapeForSCVarIfNeeded(name, esc_seq); } function escapeForSCClassIfNeeded(name, esc_seq = "ESCCLASS_") { if (isLegalSCClass(name)) { return name; } else { const escaped_name = esc_seq + name.replace(/\W/g, "_"); console.warn( `The class name "${name}" is illegal in SCLang, and will be replaced to "${escaped_name}".` ); return escaped_name; } } export { convertTSBinaryExpressionToSC, convertTSCallExpressionToSC, convertTSConditionalExpressionToSC, convertTSElementAccessExpressionToSC, convertTSExpressionToSC, convertTSNewExpressionToSC, convertTSPropertyAccessExpressionToSC, convertTSTaggedTemplateExpressionToSC, escapeForSCClassIfNeeded, escapeForSCFunctionIfNeeded, escapeForSCVarIfNeeded, hasSelfIndecrExpression, isLegalSCClass, isLegalSCIdentifier, isLegalSCVar, isMethod, isSelfIndecrExpression, isStoringObjectLiteral, supported_expression_syntax_kind, translateTSPostfixUnaryExpressionToSC, translateTSPrefixUnaryExpressionToSC, tryFindSelfIndecrOperator };