tstosc
Version:
A transpiler that convert TypeScript to SuperCollider's SCLang.
420 lines (417 loc) • 22 kB
JavaScript
import ts from 'typescript';
import { default_generator_context } from '../context.js';
import { convertTSExpressionToSC, escapeForSCVarIfNeeded, isSelfIndecrExpression } from './expr_conv.js';
import { convertTSCodeBlockToSC } from './code_block_conv.js';
import { convertTSFunctionToSC } from './literal_conv.js';
import { UnsupportedTypeError } from '../../../util/error.js';
import { solveRecurBinding, isLetDeclarationList, isAfterwhileInitInitialiser, getDefDeclOutput } from '../name_decl_def_hoist.js';
import { hash, isArrayLike } from '../../../util/util.js';
import { getTypeOfTSNode } from '../../../util/ts.js';
const supported_statement_syntax_kind = [
ts.SyntaxKind.VariableStatement,
ts.SyntaxKind.ExpressionStatement,
ts.SyntaxKind.IfStatement,
ts.SyntaxKind.DoStatement,
ts.SyntaxKind.WhileStatement,
ts.SyntaxKind.ForStatement,
ts.SyntaxKind.ForInStatement,
ts.SyntaxKind.ForOfStatement,
ts.SyntaxKind.ContinueStatement,
ts.SyntaxKind.BreakStatement,
ts.SyntaxKind.ReturnStatement,
// Not recommended and Not supported in this project: ts.SyntaxKind.WithStatement,
ts.SyntaxKind.SwitchStatement,
ts.SyntaxKind.LabeledStatement,
ts.SyntaxKind.ThrowStatement,
ts.SyntaxKind.TryStatement,
// Might be implemented in the future: ts.SyntaxKind.DebuggerStatement,
ts.SyntaxKind.EmptyStatement
];
function convertTSStatementToSC(stmt, generator_context = default_generator_context) {
const { indent_level, is_standalone_statement } = generator_context;
switch (stmt.kind) {
// `import` statement.
// TODO: If there is any need to process this kind of statement.
case ts.SyntaxKind.ImportDeclaration:
return "";
// Convert only after-while-initialiser. See function `isAfterwhileInitInitialiser`.
case ts.SyntaxKind.VariableStatement:
return restyleTSVariableStatementToSC(stmt, generator_context).indent(is_standalone_statement ? indent_level : 0);
// Might be code block used as scope in `statements`.
case ts.SyntaxKind.Block:
return convertTSCodeBlockToSC(stmt, generator_context);
case ts.SyntaxKind.FunctionDeclaration:
return converTSFunctionDeclarationToSC(stmt, generator_context);
case ts.SyntaxKind.ExpressionStatement:
return convertTSExpressionToSC(
stmt.expression,
generator_context.isStandalongStatement()
).indent(is_standalone_statement ? indent_level : 0) + " ;";
case ts.SyntaxKind.IfStatement:
return convertTSIfStatementToSC(stmt, generator_context).indent(is_standalone_statement ? indent_level : 0);
case ts.SyntaxKind.DoStatement:
return convertTSDoStatementToSC(stmt, generator_context).indent(is_standalone_statement ? indent_level : 0);
case ts.SyntaxKind.LabeledStatement:
return convertTSLabeledStatementToSC(stmt, generator_context);
case ts.SyntaxKind.WhileStatement:
return convertTSWhileStatementToSC(stmt, generator_context).indent(is_standalone_statement ? indent_level : 0);
case ts.SyntaxKind.ForStatement:
return convertTSForStatementToSC(stmt, generator_context).indent(is_standalone_statement ? indent_level : 0);
case ts.SyntaxKind.ForInStatement:
return convertTSForInStatementToSC(stmt, generator_context).indent(is_standalone_statement ? indent_level : 0);
case ts.SyntaxKind.ForOfStatement:
return convertTSForOfStatementToSC(stmt, generator_context).indent(is_standalone_statement ? indent_level : 0);
case ts.SyntaxKind.ContinueStatement:
return translateTSContinueStatementToSC(stmt, generator_context).indent(is_standalone_statement ? indent_level : 0);
case ts.SyntaxKind.BreakStatement:
return translateTSBreakStatementToSC(stmt, generator_context).indent(is_standalone_statement ? indent_level : 0);
case ts.SyntaxKind.ReturnStatement:
return translateTSReturnStatementToSC(stmt, generator_context).indent(is_standalone_statement ? indent_level : 0);
case ts.SyntaxKind.SwitchStatement:
return convertTSSwitchStatementToSC(stmt, generator_context).indent(is_standalone_statement ? indent_level : 0);
case ts.SyntaxKind.ThrowStatement:
return convertTSThrowStatementToSC(stmt, generator_context).indent(is_standalone_statement ? indent_level : 0);
case ts.SyntaxKind.TryStatement:
return convertTSTryStatementToSC(stmt, generator_context).indent(is_standalone_statement ? indent_level : 0);
// Solved by class definition generation (to User Extension Dir).
case ts.SyntaxKind.ClassDeclaration:
// SCLang does not check type.
case ts.SyntaxKind.InterfaceDeclaration:
case ts.SyntaxKind.TypeAliasDeclaration:
// No need to transpile.
case ts.SyntaxKind.EmptyStatement:
return "";
case ts.SyntaxKind.WithStatement:
case ts.SyntaxKind.DebuggerStatement:
default:
throw UnsupportedTypeError.forNodeWithSyntaxKind(stmt, "statement");
}
}
function restyleTSVariableStatementToSC(s, generator_context = default_generator_context, force_conversion = false) {
let collection = [];
for (const d of s.declarationList.declarations) {
if (ts.isIdentifier(d.name)) {
collection.push([d.name.text, d.initializer]);
} else {
collection.push(...solveRecurBinding(d.name, d.initializer));
}
}
const hint = isLetDeclarationList(s.declarationList) ? "let" : "const";
return collection.filter(
(p) => p[1] != void 0 && (force_conversion || isAfterwhileInitInitialiser(p[1]))
).map(([n, v]) => {
const converted_stmts = convertTSExpressionToSC(v, generator_context).split(";\n");
const conv_stmt_until_last = converted_stmts.slice(0, -1).join(";\n");
return conv_stmt_until_last + (conv_stmt_until_last.length > 0 ? ";\n" : "") + `/* ${hint} */ ${n} = ${converted_stmts.at(-1)} ;`;
}).join("\n");
}
function converTSFunctionDeclarationToSC(s, generator_context = default_generator_context) {
if (s.name == void 0 || s.body == void 0) {
return "";
} else {
return `${s.name.text} = ` + convertTSFunctionToSC(s, generator_context.atValuePosition()) + " ;";
}
}
function convertTSLabeledStatementToSC(s, generator_context = default_generator_context) {
return convertTSStatementToSC(s.statement, generator_context.withStatementLabel(s.label.text));
}
function convertIncrementor(incrementor, generator_context) {
if (isSelfIndecrExpression(incrementor)) {
const operator = incrementor.operator == ts.SyntaxKind.PlusPlusToken ? "+" : "-";
const target = convertTSExpressionToSC(incrementor.operand, generator_context.atValuePosition());
return `/* increment */
${target} = ${target} ${operator} 1 `;
} else {
return convertTSExpressionToSC(incrementor, generator_context.isStandalongStatement());
}
}
function convertControlFlowBody(b, generator_context, incrementor, is_a_loop = false) {
if (b == void 0) {
return "{ }";
}
const { statement_label, is_nested_loop } = generator_context;
let generator_context_new = generator_context.withStatementLabel("").isNestedLoop().clearedStatementLabel();
if (generator_context_new.with_loop_interrupt == null) {
if (isArrayLike(b)) {
generator_context_new.with_loop_interrupt = false;
for (let i = 0; i < b.length; i++) {
if (ts.isBreakOrContinueStatement(b[i])) {
generator_context_new.with_loop_interrupt = true;
break;
}
}
} else {
generator_context_new.with_loop_interrupt = ts.isBlock(b) ? b.forEachChild(
function traverse(n) {
if (ts.isBreakOrContinueStatement(n)) {
return true;
} else {
return n.forEachChild(traverse) ?? false;
}
}
) ?? false : ts.isBreakOrContinueStatement(b);
}
}
let result = "";
if (isArrayLike(b)) {
let converted = [];
for (let i = 0; i < b.length; i++) {
converted.push(convertTSStatementToSC(b[i], generator_context_new));
}
result = converted.join("\n");
} else if (ts.isBlock(b)) {
result = convertTSCodeBlockToSC(b, generator_context_new);
} else if (!ts.isEmptyStatement(b)) {
result = convertTSStatementToSC(b, generator_context_new.isStandalongStatement(false));
}
if (generator_context_new.with_loop_interrupt && is_a_loop) {
result = "/* loop body */\nvar tstosc__loop_should_break = block { |loop_end|\n" + // If there is also a label, put it before all statement.
((statement_label != "" ? `var /* label */ tstosc__label__${statement_label} = loop_end ;
` : "") + result + "\n/* Dafault value for tstosc__loop_should_break */\nfalse ;").indent(1) + `
} ;
/* Loop-Should-Break Checkpoint */
if (tstosc__loop_should_break) { ${is_nested_loop ? "loop_end.value(true) ;" : "^nil ;"} } ;`;
}
result += incrementor != void 0 ? "\n" + convertIncrementor(incrementor, generator_context_new) + " ;" : "";
const stmt_len = result.split("\n").length;
if (stmt_len > 1) {
return "{\n" + result.indent(stmt_len > 1 ? 1 : 0) + "\n}";
} else if (stmt_len == 1) {
return "{ " + result + " }";
} else {
return "{ }";
}
}
function convertTSIfStatementToSC(s, generator_context = default_generator_context) {
const { is_standalone_statement, indent_level, statement_label } = generator_context;
const generator_context_new = generator_context.clearedStatementLabel();
const sep = is_standalone_statement ? "\n" : " ";
let result = "if(" + (is_standalone_statement ? "\n" : "") + convertTSExpressionToSC(s.expression, generator_context_new).indent(indent_level + 1) + "," + sep + convertControlFlowBody(s.thenStatement, generator_context_new).indent(indent_level + 1) + "," + sep + convertControlFlowBody(s.elseStatement, generator_context_new).indent(indent_level + 1) + (is_standalone_statement ? "\n)" : ")") + " ;";
result = wrapIfLabelExist(result, statement_label);
return result;
}
function convertTSDoStatementToSC(s, generator_context = default_generator_context) {
const iter_body_name = "~tstosc__do_body__" + hash(s.statement).slice(0, 8);
const iter_body_run = iter_body_name + ".()";
return iter_body_name + ` = ${convertControlFlowBody(s.statement, generator_context.makeBreakMeans("stop_a_loop"), void 0, true)} ;
` + iter_body_run + ` ;
while({ ${convertTSExpressionToSC(s.expression, generator_context)} }, { ${iter_body_run} }) ;`;
}
function convertTSWhileStatementToSC(s, generator_context = default_generator_context) {
const { is_standalone_statement, indent_level } = generator_context;
const raw_condition = convertTSExpressionToSC(s.expression, generator_context);
const condition = raw_condition.includes("\n") ? "{\n" + raw_condition.indent(1) + "\n}" : "{ " + raw_condition + " },";
const body = convertControlFlowBody(s.statement, generator_context.makeBreakMeans("stop_a_loop"), void 0, true);
return "while(" + (is_standalone_statement ? "\n" : "") + condition.indent(indent_level + 1) + (is_standalone_statement ? "\n" : " ") + body.indent(indent_level + 1) + (is_standalone_statement ? "\n)" : ")") + " ;";
}
function convertTSForStatementToSC(s, generator_context = default_generator_context) {
const { is_standalone_statement, indent_level } = generator_context;
const generator_context_new = generator_context.makeBreakMeans("stop_a_loop");
const sep = is_standalone_statement ? "\n" : " ";
function convertForInitialiser(init_part2) {
if (init_part2 == void 0) {
return "";
}
if (ts.isVariableDeclarationList(init_part2)) {
return restyleTSVariableStatementToSC(
ts.factory.createVariableStatement(void 0, init_part2),
generator_context_new,
true
// Forcing the conversion.
);
} else if (ts.isExpression(init_part2)) {
return convertTSExpressionToSC(init_part2, generator_context_new);
} else {
throw UnsupportedTypeError.forNodeWithSyntaxKind(init_part2, "for statement initialiser");
}
}
function convertForCondition(cond) {
if (cond == void 0) {
return "";
} else {
return `{ ${convertTSExpressionToSC(cond, generator_context_new)} }`;
}
}
const init_part = convertForInitialiser(s.initializer);
return init_part + (init_part.length > 0 ? "\n" : "") + "while(" + (is_standalone_statement ? "\n " : "") + convertForCondition(s.condition) + "," + sep + convertControlFlowBody(s.statement, generator_context_new, s.incrementor, true).indent(indent_level + 1) + (is_standalone_statement ? "\n)" : ")") + " ;";
}
function convertTSForInOrForOfStatement__Impl(s, kind, generator_context = default_generator_context) {
const body = convertControlFlowBody(
s.statement,
generator_context.outdent(),
/* incrementor */
void 0,
/* is_a_loop */
true
).replace(/^{\s?|\s?}$/g, "");
const is_multi_line_body = body.includes("\n");
const sep = is_multi_line_body ? "\n" : " ";
const is_complex_initialiser = !(ts.isVariableDeclarationList(s.initializer) && ts.isIdentifier(
s.initializer.declarations[0].name
)) && !ts.isIdentifier(s.initializer);
const initialiser_arg = is_complex_initialiser ? "tstosc__loopvar" : escapeForSCVarIfNeeded((ts.isVariableDeclarationList(s.initializer) ? s.initializer.declarations[0].name : s.initializer).text);
const loopvar_pattern_solving = is_complex_initialiser ? getDefDeclOutput("loopvar", solveRecurBinding(
s.initializer.declarations[0].name,
{
...ts.factory.createIdentifier("tstosc__loopvar"),
kind: ts.SyntaxKind.Identifier,
text: "tstosc__loopvar",
forEachChild: () => void 0
}
)) : "";
return convertTSExpressionToSC(s.expression, generator_context.clearedStatementLabel()) + ".do" + sep + (kind == "for-in" ? "{ /* for-in */ |tstosc__drop_arg, " + initialiser_arg + "|" + sep : "{ /* for-of */ |" + initialiser_arg + ", tstosc__drop_arg|" + sep) + (loopvar_pattern_solving != "" ? loopvar_pattern_solving.indent(1) + sep : "") + body.indent(is_multi_line_body ? 1 : 0) + sep + "}";
}
function convertTSForInStatementToSC(s, generator_context = default_generator_context) {
return convertTSForInOrForOfStatement__Impl(s, "for-in", generator_context);
}
function convertTSForOfStatementToSC(s, generator_context = default_generator_context) {
return convertTSForInOrForOfStatement__Impl(s, "for-of", generator_context);
}
function translateTSContinueStatementToSC(s, generator_context = default_generator_context) {
return "/* continue */ " + (s.label != void 0 ? `tstosc__label__${s.label.text}` : "loop_end") + ".value(false) ;";
}
function translateTSBreakStatementToSC(s, generator_context = default_generator_context) {
const break_means = s.label == void 0 ? generator_context.break_means : "end_label";
switch (break_means) {
// But if the `break` comes with a label, that means breaking
case "end_label":
case "stop_a_loop":
return "/* break */ " + (s.label != void 0 ? `tstosc__label__${s.label.text}` : "loop_end") + ".value(true) ;";
// A early break of a switch's case.
case "end_switch_case":
return "/* break */ tstosc__switch_break.value(nil) ;";
case "nothing":
throw TypeError("`break` here is not associated with switch/loop.");
}
}
function translateTSReturnStatementToSC(s, generator_context = default_generator_context) {
const return_expr = s.expression != void 0 ? convertTSExpressionToSC(s.expression, generator_context.atValuePosition()) : "nil /* no-expr return */";
if (generator_context.is_generating_method) {
return "^" + return_expr;
} else {
return (generator_context.with_early_return ? `return_with.value(${return_expr})` : return_expr) + " ;";
}
}
function convertTSSwitchStatementToSC(s, generator_context = default_generator_context) {
const { statement_label } = generator_context;
const generator_context_new = generator_context.makeBreakMeans("end_switch_case").clearedStatementLabel();
const case_clause_pack = s.caseBlock.clauses.map(
function(c) {
const break_stmt_index = c.statements.findIndex(
(s2) => (
// TODO: still problematic.
ts.isBreakStatement(s2) || s2.forEachChild(
function findEarlyBreak(n) {
if (ts.isBreakStatement(s2)) {
return true;
} else {
return n.forEachChild(findEarlyBreak) ?? false;
}
}
)
)
);
const has_early_break = break_stmt_index >= 0 && break_stmt_index < c.statements.length - 1;
const is_case_clause = ts.isCaseClause(c);
const last_stmt = c.statements.at(-1);
const has_trailing_break = last_stmt != void 0 && ts.isBreakStatement(last_stmt) && last_stmt.label == void 0;
const is_fallthrough = is_case_clause && !has_early_break && !has_trailing_break && c.statements.length == 0;
return {
c,
/* Check if there is a `break` statement that ends case earlier. */
has_early_break,
has_trailing_break,
is_fallthrough,
is_case_clause,
condition_value: is_case_clause ? convertTSExpressionToSC(c.expression, generator_context_new) : "/* otherwise */ 0/0",
// Since `NaN` does not equal to itself. Useful for fallthrough to `default`.
fallthrough_jump_value: null
};
}
).reduceRight(
function(accu, curr) {
if (curr.is_case_clause && curr.is_fallthrough) {
const prev = accu.at(-1);
curr.fallthrough_jump_value = prev == void 0 ? curr.condition_value : prev.is_fallthrough ? prev.fallthrough_jump_value : prev.condition_value;
}
accu.push(curr);
return accu;
},
[]
).reverse();
function convertCase({
c,
has_early_break,
has_trailing_break,
is_case_clause,
condition_value,
is_fallthrough,
fallthrough_jump_value
}, index, clauses_pack_array) {
const hint = is_case_clause ? "/* case */ " : "/* otherwise */ ";
const cond = is_case_clause ? "{ " + condition_value + " }," : "";
const body_raw = is_fallthrough ? `{ /* fallthough */ thisFunction.value(${fallthrough_jump_value}); }` : convertControlFlowBody(has_trailing_break ? c.statements.slice(0, -1) : c.statements, generator_context_new);
const body = has_early_break ? `{ block { |tstosc__switch_break|
${body_raw.replace(/^{\s|\s}$/g, "")}
} }` : body_raw;
const sep = body.includes("\n") ? "\n" : " ";
return (hint + cond + sep + body).indent(1);
}
const value_to_test = convertTSExpressionToSC(s.expression, generator_context_new);
const converted_clauses = case_clause_pack.map((c, index, arr) => convertCase(c)).join(",\n");
let result = "";
if (case_clause_pack.some((p) => p.is_fallthrough)) {
result = `{ |tstosc__test_value| /* switch(${value_to_test}) */
` + ("switch( tstosc__test_value,\n" + converted_clauses + "\n) ;").indent(1) + `
}.value(${value_to_test}) ;`;
} else {
result = "switch( " + value_to_test + ",\n" + converted_clauses + "\n) ;";
}
result = wrapIfLabelExist(result, statement_label);
return result;
}
function wrapIfLabelExist(converted, label) {
if (label == "" || label == void 0) {
return converted;
} else {
return `block { |tstosc__label__${label}|
${converted.indent(1)}
} ;`;
}
}
function convertTSThrowStatementToSC(s, generator_context = default_generator_context) {
const expr_type = getTypeOfTSNode(generator_context.compiler_program, s.expression);
if ((expr_type.getBaseTypes()?.map((t) => t.symbol.name).includes("Error") ?? false) || expr_type.symbol.name == "Error") {
return `${convertTSExpressionToSC(s.expression, generator_context)}.throw() ;`;
} else {
return `Error(${convertTSExpressionToSC(s.expression, generator_context)}).throw() ;`;
}
}
function convertTSTryStatementToSC(s, generator_context = default_generator_context) {
const { statement_label } = generator_context;
const generator_context_new = generator_context.clearedStatementLabel();
const try_block = convertControlFlowBody(s.tryBlock, generator_context_new);
const has_catch_block = s.catchClause != void 0;
const has_catch_var = s.catchClause?.variableDeclaration != void 0;
const is_catch_var_identifier = has_catch_var && ts.isIdentifier(s.catchClause.variableDeclaration.name);
const catch_block_raw = has_catch_block ? convertControlFlowBody(s.catchClause.block, generator_context_new) : "";
const catch_var = has_catch_block && has_catch_var ? is_catch_var_identifier ? escapeForSCVarIfNeeded(s.catchClause.variableDeclaration.name.text) : "tstosc__catchvar" : "";
const catch_var_solving_part = has_catch_block && has_catch_var && !is_catch_var_identifier ? "\n" + getDefDeclOutput("catchvar", solveRecurBinding(
s.catchClause.variableDeclaration.name,
{
...ts.factory.createIdentifier("tstosc__catchvar"),
kind: ts.SyntaxKind.Identifier,
text: "tstosc__catchvar",
forEachChild: () => void 0
}
), generator_context_new) : "";
const catch_block = has_catch_block ? catch_block_raw.replace(/^{/g, `{ |${catch_var}|${catch_var_solving_part}`) : "";
let result = "try" + (try_block.includes("\n") ? "\n" : " ") + try_block + "\n/* catch */" + (catch_block.includes("\n") ? "\n" : " ") + (has_catch_block ? catch_block : " { }");
if (s.finallyBlock != void 0) {
const finally_block = convertTSCodeBlockToSC(s.finallyBlock, generator_context_new);
result = "protect\n{" + result.indent(1) + "}\n/* finally */\n{" + finally_block.indent(1) + "} ;";
}
result = wrapIfLabelExist(result, statement_label);
return result;
}
export { converTSFunctionDeclarationToSC, convertTSDoStatementToSC, convertTSForInStatementToSC, convertTSForOfStatementToSC, convertTSForStatementToSC, convertTSIfStatementToSC, convertTSLabeledStatementToSC, convertTSStatementToSC, convertTSSwitchStatementToSC, convertTSThrowStatementToSC, convertTSTryStatementToSC, convertTSWhileStatementToSC, restyleTSVariableStatementToSC, supported_statement_syntax_kind, translateTSBreakStatementToSC, translateTSContinueStatementToSC, translateTSReturnStatementToSC };