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