tstosc
Version:
A transpiler that convert TypeScript to SuperCollider's SCLang.
208 lines (205 loc) • 10 kB
JavaScript
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 };