UNPKG

tstosc

Version:

A transpiler that convert TypeScript to SuperCollider's SCLang.

208 lines (205 loc) 10 kB
import ts from 'typescript'; import { default_generator_context } from '../context.js'; import { UnsupportedTypeError } from '../../../util/error.js'; import { escapeForSCClassIfNeeded, convertTSExpressionToSC, escapeForSCVarIfNeeded } from './expr_conv.js'; import { convertTSCodeBlockToSC } from './code_block_conv.js'; import { convertTSFunctionToSC, extractParameters, getArgLine, getDestructingParamSolvingPart } from './literal_conv.js'; import { extractVarConstDeclarationAndDefinition } from '../name_decl_def_hoist.js'; let unnamed_class_index = 1; function convertTSClassToSC(c, generator_context = default_generator_context) { return convertTSClassToSCWithClassName(c, c.name?.text ?? `UnnamedClass${unnamed_class_index++}`, generator_context); } function convertTSClassToSCWithClassName(c, name, generator_context = default_generator_context) { const inherits_part = convertInheritsPart( c.heritageClauses != void 0 ? c.heritageClauses.find((c2) => c2.token == ts.SyntaxKind.ExtendsKeyword)?.types[0].expression : null ); const generator_context_new = generator_context.withClassInfo({ name, super_class_name: inherits_part.replace(": ", "") }).makeSuperMeans("as_it_is"); const class_member_part = convertClassMembersOf(c, generator_context_new).indent(1); return `${escapeForSCClassIfNeeded(name)} ${inherits_part} { ` + class_member_part + "\n}"; } function convertInheritsPart(inherits_part) { if (inherits_part == null || inherits_part == void 0) { return ""; } let result = ""; switch (inherits_part.kind) { case ts.SyntaxKind.Identifier: result = inherits_part.text; break; // TODO: with module access: should resolve to `TSTOSC_Impl_` class name, rather than access the module // (because SCLang only accept Identifier after `:`). default: throw UnsupportedTypeError.forNodeWithSyntaxKind(inherits_part, "inheriting class expression"); } return ": " + escapeForSCClassIfNeeded(result); } function getNameFromPropertyName(name) { switch (true) { case ts.isPrivateIdentifier(name): console.warn( `SCLang does not support private-identifier in class.`, `Converting "${name.text}" to normal members.` ); case (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNoSubstitutionTemplateLiteral(name)): return escapeForSCVarIfNeeded(name.text); default: throw UnsupportedTypeError.forNodeWithSyntaxKind(name, "class member name"); } } function convertClassMembersOf(c, generator_context) { let var_like_decl = []; let set_get_decl = []; let method_like_decl = []; for (const member of c.members) { const has_modifier = function(member2) { return "modifiers" in member2 && member2.modifiers != void 0; }(member); const is_static = has_modifier && member.modifiers.some((x) => x.kind == ts.SyntaxKind.StaticKeyword); has_modifier && member.modifiers.some((x) => x.kind == ts.SyntaxKind.ReadonlyKeyword); const init_part = "initializer" in member && member.initializer != void 0 ? ` = ${convertTSExpressionToSC(member.initializer, generator_context)}` : ""; switch (true) { case ts.isAutoAccessorPropertyDeclaration(member): case ts.isPropertyDeclaration(member): { const name = getNameFromPropertyName(member.name); const stored_name = "prtstosc__store__" + name; var_like_decl.push(`${is_static ? "classvar" : "var"} <>${stored_name}${init_part} ;`); set_get_decl.push( `${name} { ^this.${stored_name} ; } ${name}_ { |tstosc__setter_arg| this.${stored_name} = tstosc__setter_arg ; }` ); break; } case ts.isMethodDeclaration(member): { const name = getNameFromPropertyName(member.name); const generator_context_new = (is_static ? generator_context.makeSuperMeans("class_name") : generator_context).willGenerateMethod().makeThisComingFrom("itself"); const body = member.body != void 0 ? convertTSFunctionToSC(member, generator_context_new) : "{ }"; const sep = body.includes("\n") ? "\n" : " "; method_like_decl.push(name + sep + body); break; } case ts.isGetAccessorDeclaration(member): { const name = getNameFromPropertyName(member.name); const body = member.body != void 0 ? convertTSFunctionToSC( member, generator_context.willGenerateMethod().makeThisComingFrom("itself") ) : "{ }"; const sep = body.includes("\n") ? "\n" : " "; set_get_decl.push(name + sep + body); break; } case ts.isSetAccessorDeclaration(member): { const name = getNameFromPropertyName(member.name); const body = member.body != void 0 ? convertTSFunctionToSC( member, generator_context.willGenerateMethod().makeThisComingFrom("itself") ) : "{ }"; const sep = body.includes("\n") ? "\n" : " "; set_get_decl.push(name + "_" + sep + body); break; } case ts.isConstructorDeclaration(member): { if (member.body == void 0) { continue; } method_like_decl.push(...convertConstructor(member, generator_context)); break; } case ts.isClassStaticBlockDeclaration(member): { const body = member.body != void 0 ? convertTSCodeBlockToSC(member.body, generator_context) : "{ }"; const sep = body.includes("\n") ? "\n" : " "; method_like_decl.push("*initClass" + sep + body); break; } // Empty statement. case ts.isSemicolonClassElement(member): continue; default: throw UnsupportedTypeError.forNodeWithSyntaxKind(member, "class member"); } } return [ var_like_decl.join("\n"), set_get_decl.join("\n"), method_like_decl.join("\n") ].filter((s) => s.length > 0).join("\n\n"); } function convertConstructor(c, generator_context) { if (c.body == void 0) { return ""; } generator_context = generator_context.willGenerateConstructor().willGenerateMethod().makeThisComingFrom("built_instance"); const last_super_call__index = c.body.statements.findLastIndex( function findSuperCall(s) { if (ts.isCallExpression(s) && s.expression.kind == ts.SyntaxKind.SuperKeyword) { return true; } else { return s.forEachChild(findSuperCall) ?? false; } } ) ?? 0; const former_statement = c.body.statements.slice(0, last_super_call__index + 1); const latter_statement = c.body.statements.slice(last_super_call__index + 1); const init__method_name = `initTStoSC${generator_context.class_info.name}`; const { trivial_params, arg_omittable_params, destruct_params, collecting_param } = extractParameters(c); const new__arg_line = getArgLine(trivial_params, arg_omittable_params, destruct_params, collecting_param, generator_context); const new__destruct_solving_part = getDestructingParamSolvingPart(destruct_params, generator_context); const { lets, consts, vars, functions } = extractVarConstDeclarationAndDefinition(ts.factory.createBlock(former_statement), generator_context); const new__body_env_dict = ts.factory.createObjectLiteralExpression([ ...lets, ...consts, ...vars, ...functions ].map(([n]) => ts.factory.createPropertyAssignment(n, ts.factory.createIdentifier(n)))); const arg_for_initClassName = new__arg_line.replace(/^\s?arg|;$/g, "").split(",").filter((s) => s.length > 0).map((a) => ts.factory.createIdentifier(a.trim().replace(/\s?\=\s?.+$/g, ""))); let new__body = convertTSCodeBlockToSC(ts.factory.createBlock([ // Use a variable to store the result coming from `super()` call. ts.factory.createVariableStatement( void 0, ts.factory.createVariableDeclarationList([ ts.factory.createVariableDeclaration("tstosc__built_instance") ], ts.NodeFlags.Let) ), // Statements that contains `super()` call. ...former_statement, // Call `initClassName` here. ts.factory.createReturnStatement(ts.factory.createCallExpression( ts.factory.createPropertyAccessExpression( ts.factory.createIdentifier("tstosc__built_instance"), init__method_name ), void 0, // args for `initTStoSCClassName`: [...arg_for_initClassName, new__body_env_dict] )) ]), generator_context); const is_new_body_mul_line = new__destruct_solving_part.includes("\n") || new__body.includes("\n"); const sep__new_body = is_new_body_mul_line ? "\n" : " "; new__body = "{" + new__arg_line + sep__new_body + (new__destruct_solving_part != "" ? new__destruct_solving_part + sep__new_body : "") + new__body.indent(is_new_body_mul_line ? 1 : 0) + sep__new_body + "}"; const init__destruct_params = [...destruct_params, ts.factory.createParameterDeclaration(void 0, void 0, ts.factory.createObjectBindingPattern([ ...lets, ...consts, ...vars, ...functions ].map(([n]) => ts.factory.createBindingElement(void 0, n, n))))]; const init__arg_line = getArgLine(trivial_params, arg_omittable_params, init__destruct_params, collecting_param, generator_context); const init__destruct_solving_part = getDestructingParamSolvingPart(init__destruct_params, generator_context); let init__body = convertTSCodeBlockToSC(ts.factory.createBlock([ ...latter_statement, ts.factory.createReturnStatement(ts.factory.createThis()) ]), generator_context.makeThisComingFrom("itself")); const is_init_body_mul_line = init__destruct_solving_part.includes("\n") || init__body.includes("\n"); const sep__init_body = is_init_body_mul_line ? "\n" : " "; init__body = "{" + init__arg_line + sep__init_body + (init__destruct_solving_part != "" ? init__destruct_solving_part + sep__init_body : "") + init__body.indent(is_init_body_mul_line ? 1 : 0) + sep__init_body + "}"; return [ "*new" + sep__new_body + new__body, init__method_name + sep__init_body + init__body ]; } export { convertTSClassToSC, convertTSClassToSCWithClassName };