UNPKG

tstosc

Version:

A transpiler that convert TypeScript to SuperCollider's SCLang.

321 lines (318 loc) 14.2 kB
import ts from 'typescript'; import { UnsupportedTypeError } from '../../../util/error.js'; import { Result } from '../../../util/Result.js'; import { convertTSExpressionToSC, escapeForSCVarIfNeeded } from './expr_conv.js'; import { default_generator_context } from '../context.js'; import { hasEarlyReturnIn, convertTSCodeBlockToSC } from './code_block_conv.js'; import { getDefDeclOutput, solveRecurBinding } from '../name_decl_def_hoist.js'; import { bifilter } from '../../../util/util.js'; const supported_literal_syntax_kind = [ // These are from `ts.LiteralSyntaxKind` ts.SyntaxKind.NumericLiteral, ts.SyntaxKind.BigIntLiteral, ts.SyntaxKind.StringLiteral, ts.SyntaxKind.RegularExpressionLiteral, ts.SyntaxKind.NoSubstitutionTemplateLiteral, // These are extended to make the program easy-to-write-and-understand. ts.SyntaxKind.TemplateExpression, ts.SyntaxKind.ArrayLiteralExpression, ts.SyntaxKind.ObjectLiteralExpression, ts.SyntaxKind.FunctionExpression, ts.SyntaxKind.ArrowFunction, // ts.SyntaxKind.ClassDeclaration, ts.SyntaxKind.ClassExpression, ts.SyntaxKind.TrueKeyword, ts.SyntaxKind.FalseKeyword, ts.SyntaxKind.NullKeyword ]; function convertTSLiteralToSC(literal, generator_context = default_generator_context) { switch (literal.kind) { case ts.SyntaxKind.NumericLiteral: case ts.SyntaxKind.BigIntLiteral: return convertTSNumberToSC(literal, generator_context); case ts.SyntaxKind.StringLiteral: case ts.SyntaxKind.NoSubstitutionTemplateLiteral: return `"${literal.text}"`; case ts.SyntaxKind.TemplateExpression: return convertTSTemplateStringToSC(literal, generator_context); case ts.SyntaxKind.TrueKeyword: return `true`; case ts.SyntaxKind.FalseKeyword: return `false`; case ts.SyntaxKind.ArrayLiteralExpression: return convertTSArrayToSC(literal, generator_context); case ts.SyntaxKind.ObjectLiteralExpression: return convertTSObjectToSC(literal, generator_context); case ts.SyntaxKind.ArrowFunction: case ts.SyntaxKind.FunctionExpression: return convertTSFunctionToSC(literal, generator_context); case ts.SyntaxKind.RegularExpressionLiteral: // TODO: Not supported yet. default: if (isNullOrUndefined(literal)) { console.warn( `Please use SCLang's "nil" instead of TypeScript's "null" or "undefined",`, ` since SCLang only have "nil" for empty values,`, ` and using "null" or "undefined" may result in confusing result.` ); return `nil`; } throw UnsupportedTypeError.forNodeWithSyntaxKind(literal, "literal"); } } function isNullOrUndefined(node) { return node.kind == ts.SyntaxKind.NullKeyword || ts.isIdentifier(node) && node.text == "undefined"; } function convertTSNumberToSC(literal, generator_context = default_generator_context) { let force_to_float_suffix = ""; let number_part = literal.text.replace(/^(?:0[box]?)+(?!\.|$)|n$/g, ""); let radix_prefix = ""; const check_safe_int_result = assertSafeIntegerInSC(literal); if (check_safe_int_result.isErr()) { force_to_float_suffix = ".0"; let precision_lost_hint; switch (check_safe_int_result.unwrapErr()) { case 1 /* out_of_i64 */: precision_lost_hint = "with lost of precision"; break; case 0 /* out_of_i32_inside_i64 */: precision_lost_hint = "without lost of precision"; break; } const line_col_pos = ts.getLineAndCharacterOfPosition(literal.getSourceFile(), literal.pos); console.warn( `The number ${literal.getFullText().trim()} `, `at line ${line_col_pos.line}, column ${line_col_pos.character}, `, `will be converted to 64-digit float number ${precision_lost_hint}.` ); } switch (ts.isBigIntLiteral(literal) ? getRadix(literal) : literal.numericLiteralFlags) { // These could be converted directly. case ts.TokenFlags.None: // Decimals. case ts.TokenFlags.Scientific: break; case ts.TokenFlags.HexSpecifier: radix_prefix = "0x"; break; // These need to use sclang's r-prefix expression. case ts.TokenFlags.OctalSpecifier: case ts.TokenFlags.Octal: radix_prefix = "8r"; break; case ts.TokenFlags.BinarySpecifier: radix_prefix = "2r"; break; } return `${radix_prefix}${number_part}${force_to_float_suffix}`; } function assertSafeIntegerInSC(literal) { let n = literal.kind == ts.SyntaxKind.BigIntLiteral ? BigInt(literal.text.slice(0, -1)) : Number(literal.text); if (n < Math.pow(2, 32) - 1 && n > -Math.pow(2, 32)) { return Result.createOk(null); } else if (n < Math.pow(2, 64) - 1 && n > -Math.pow(2, 64)) { return Result.createErr(0 /* out_of_i32_inside_i64 */); } else { return Result.createErr(1 /* out_of_i64 */); } } function getRadix(literal) { switch (/^0[box]/g.exec(literal.getFullText())?.[0]) { case "0b": return ts.TokenFlags.BinarySpecifier; case "0": // This should not present in TS. For JS/TS syntax design reason, this `case` is remained. case "0o": return ts.TokenFlags.OctalSpecifier; case "0x": return ts.TokenFlags.HexSpecifier; default: return ts.TokenFlags.None; } } function convertTSTemplateStringToSC(literal, generator_context = default_generator_context) { if (ts.isNoSubstitutionTemplateLiteral(literal)) { return `"${literal.getText()}"`; } return [ `"${literal.head.text}"`, ...literal.templateSpans.flatMap( (s) => { const l_text = s.literal.text; if (l_text.length > 0) { return [convertTSExpressionToSC(s.expression), `"${l_text}"`]; } else { return convertTSExpressionToSC(s.expression); } } ) ].join(" ++ "); } function convertTSArrayToSC(literal, generator_context = default_generator_context) { return literal.elements.reduce( function(result_until_now, current, current_index) { const is_last_element = current_index == literal.elements.length - 1; if (ts.isSpreadElement(current)) { result_until_now.push( "] ++ ", convertTSExpressionToSC(current.expression, generator_context), " ++ [" ); } else { result_until_now.push( convertTSExpressionToSC(current, generator_context), is_last_element ? "" : ", " ); } if (is_last_element) { result_until_now.push("]"); } return result_until_now; }, ["["] // Initial value. ).join(""); } function convertTSObjectToSC(literal, generator_context = default_generator_context) { generator_context = generator_context.willGenerateMemberOfObjectLiteral().makeThisComingFrom("object_literal_parameter"); function convertAssignmentPair(p) { let name; let value; function getPropertyName(p2) { switch (true) { case ts.isPrivateIdentifier(p2.name): console.warn( `SCLang does not support private members in Dictionary.`, `Converting "${p2.name.text}" to normal members.` ); case ts.isIdentifier(p2.name): return `"${p2.name.text}"`; case isTSLiteral(p2.name): return convertTSExpressionToSC(p2.name); case ts.isComputedPropertyName(p2.name): return convertTSExpressionToSC(p2.name.expression); default: throw UnsupportedTypeError.forNodeWithSyntaxKind(p2.name, "property assignment"); } } if (ts.isPropertyAssignment(p)) { name = getPropertyName(p); value = convertTSExpressionToSC(p.initializer, generator_context); } else if (ts.isShorthandPropertyAssignment(p)) { value = p.name.text; name = `"${value}"`; } else if (ts.isMethodDeclaration(p)) { name = getPropertyName(p); value = p.body != void 0 ? convertTSFunctionToSC(p, generator_context.willGenerateMethod()) : "{ |tstosc__this_param| }"; } else { throw UnsupportedTypeError.forNodeWithSyntaxKind(p); } return `${name} -> ${value}`; } const [spread_assignment_property, non_spread_property] = bifilter(literal.properties, (p) => ts.isSpreadAssignment(p)); const non_spread_part = non_spread_property.map((p) => convertAssignmentPair(p)).join(", "); const spread_part = spread_assignment_property.map((p) => `.putAll(${convertTSExpressionToSC(p.expression, generator_context)})`).join(""); return `TSTOSC__ObjectLiteral.new(Dictionary[${non_spread_part}]${spread_part})`; } function getArgLine(trivial_params, arg_omittable_params, destruct_params, collecting_param, generator_context) { const args = [ trivial_params.map((p) => escapeForSCVarIfNeeded(p.name.text)).join(", "), arg_omittable_params.map( (p) => `${escapeForSCVarIfNeeded(p.name.text)}=${convertTSExpressionToSC(p.initializer, generator_context)}` ).join(", "), destruct_params.map( (p, index) => `tstosc_dvar_${index}` + (p.initializer != void 0 ? ` = ${convertTSExpressionToSC(p.initializer)}` : "") ).join(", "), collecting_param != null ? `*${escapeForSCVarIfNeeded(collecting_param?.name.text ?? "tstosc_cvar")}` : "" ].filter((r) => r.length > 0).join(", "); return args.length != 0 ? " arg " + args + " ;" : ""; } function getDestructingParamSolvingPart(destruct_params, generator_context) { const result = destruct_params.length > 0 ? destruct_params.map((p, index) => getDefDeclOutput( "param", solveRecurBinding(p.name, ts.factory.createIdentifier(`tstosc_dvar_${index}`)), generator_context.indent().isStandalongStatement() )).join(" ;\n") : ""; return result != "" ? result + "\n" : ""; } function convertTSFunctionToSCWithParam(literal, trivial_params, arg_omittable_params, destruct_params, collecting_param, generator_context = default_generator_context) { const { indent_level, is_standalone_statement } = generator_context; const sc_arg_line = literal.parameters.length > 0 || ([...trivial_params, ...arg_omittable_params, ...destruct_params].length > 0 || collecting_param != null) ? getArgLine(trivial_params, arg_omittable_params, destruct_params, collecting_param, generator_context) : ""; const destruct_solving_part = getDestructingParamSolvingPart(destruct_params, generator_context); let sc_function_body; if (ts.isArrowFunction(literal) && ts.isExpression(literal.body)) { sc_function_body = convertTSExpressionToSC(literal.body, generator_context); return `{${sc_arg_line} ${destruct_solving_part.replace("\n", "")} ${sc_function_body} }`.indent(is_standalone_statement ? indent_level : 0); } else if (ts.isBlock(literal.body)) { const is_early_return = generator_context.with_early_return || !generator_context.is_generating_method && hasEarlyReturnIn(literal.body.statements); sc_function_body = convertTSCodeBlockToSC( literal.body, generator_context.isStandalongStatement().indent().withEarlyReturn(is_early_return) ); const is_body_mul_line = destruct_solving_part.includes("\n") || sc_function_body.includes("\n"); const sep = is_body_mul_line ? "\n" : " "; if (!is_body_mul_line) { sc_function_body = sc_function_body.trim(); } return is_early_return ? `block { |return_with|`.indent(is_standalone_statement ? indent_level : 0) + "\n" + `{${sc_arg_line}`.indent(is_standalone_statement ? indent_level + 1 : 0) + "\n" + destruct_solving_part.indent(1) + sc_function_body.indent(1) + "\n" + `}`.indent(is_standalone_statement ? indent_level + 1 : 0) + "\n" + `}`.indent(is_standalone_statement ? indent_level : 0) : `{${sc_arg_line}`.indent(is_standalone_statement ? indent_level : 0) + sep + destruct_solving_part + sc_function_body + sep + `}`.indent(is_standalone_statement ? indent_level : 0); } else { throw UnsupportedTypeError.forNodeWithSyntaxKind(literal, "function literal"); } } function convertTSFunctionToSC(literal, generator_context = default_generator_context) { let { trivial_params, arg_omittable_params, destruct_params, collecting_param } = extractParameters(literal); if (generator_context.is_generating_member_of_object_literal) { trivial_params.unshift({ ...ts.factory.createParameterDeclaration(void 0, void 0, "tstosc__this_param"), kind: ts.SyntaxKind.Parameter }); } return convertTSFunctionToSCWithParam( literal, trivial_params, arg_omittable_params, destruct_params, collecting_param, generator_context ); } function convertToSCSymbol(node) { return "\\" + node.text; } function extractParameters(f) { let parameters = [...f.parameters]; let trivial_params = []; let arg_omittable_params = []; let destruct_params = []; let collecting_param = null; const last_param = f.parameters.at(-1); const has_rest_param = last_param != void 0 && ts.isRestParameter(last_param); if (has_rest_param) { collecting_param = parameters.at(-1); parameters = parameters.slice(0, -1); } for (const p of parameters) { if (ts.isIdentifier(p.name)) { if (p.initializer == void 0 && p.dotDotDotToken == void 0) { trivial_params.push(p); } else { arg_omittable_params.push(p); } } else { destruct_params.push(p); } } return { /** Which could be simply convert alike `a`. */ trivial_params, /** Which could be simply convert alike `a = nil` or `a = 0`. */ arg_omittable_params, /** Need to generate a dummy parameter, and handled inside function body. */ destruct_params, /** The rest param collecting parameter. */ collecting_param }; } function isTSLiteral(node) { return supported_literal_syntax_kind.includes(node.kind) || ts.isLiteralExpression(node) || ts.isArrayLiteralExpression(node) || ts.isObjectLiteralExpression(node) || isNullOrUndefined(node); } export { assertSafeIntegerInSC, convertTSArrayToSC, convertTSFunctionToSC, convertTSFunctionToSCWithParam, convertTSLiteralToSC, convertTSNumberToSC, convertTSObjectToSC, convertTSTemplateStringToSC, convertToSCSymbol, extractParameters, getArgLine, getDestructingParamSolvingPart, getRadix, isNullOrUndefined, isTSLiteral, supported_literal_syntax_kind };