Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
7f47f12
feat: Extend model with style information
Danielku15 Mar 6, 2025
ccd2245
feat: Add element style render helper for later use
Danielku15 Mar 6, 2025
76a1081
feat: Allow lowercase enums
Danielku15 Mar 6, 2025
816d185
refactor: Rework type system used for code generation
Danielku15 Mar 7, 2025
4981026
build: add resolving of type parameters for generic base members
Danielku15 Mar 7, 2025
bd6a93a
chore: Enable vite config debugging
Danielku15 Mar 8, 2025
8c0b492
feat: More styles
Danielku15 Mar 8, 2025
034d575
fix: No repeat open on numbered notation
Danielku15 Mar 8, 2025
adcc29f
feat: respect score styles
Danielku15 Mar 8, 2025
4ad5ca6
feat: respect track styles
Danielku15 Mar 8, 2025
1ac2958
feat: respect bar styles
Danielku15 Mar 8, 2025
04da230
feat: respect voice styles
Danielku15 Mar 8, 2025
6a5b233
feat: Staff line coloring
Danielku15 Mar 8, 2025
6f52aa8
feat: beat style rendering
Danielku15 Mar 9, 2025
53de039
feat: rewrite style helper for better runtime performance
Danielku15 Mar 9, 2025
72bae95
fix: Broken alignment
Danielku15 Mar 9, 2025
d64af14
feat: respect note styles
Danielku15 Mar 9, 2025
ba6e686
test: add render test
Danielku15 Mar 9, 2025
1ab2312
fix: some rendering problems
Danielku15 Mar 9, 2025
9a84abe
fix: changed alignment for deadslapped
Danielku15 Mar 9, 2025
afde93b
build(cross): local functions and disposable compatibility
Danielku15 Mar 9, 2025
df7506c
fix: working version on all variants
Danielku15 Mar 9, 2025
8f667ef
test: Performance of colors
Danielku15 Mar 9, 2025
1b9d8b8
test: better performance test checks on coloring
Danielku15 Mar 9, 2025
aa8b02c
build(cross): Use cross platform logger
Danielku15 Mar 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 4 additions & 7 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
{
"mochaExplorer.files": "test/**/*.test.ts",
"mochaExplorer.nodeArgv": [
"--experimental-specifier-resolution=node",
"--import=tsx"
],
"mochaExplorer.logpanel": true
}
"debug.javascript.terminalOptions": {
"resolveSourceMapLocations": ["${workspaceFolder}/**", "!**/node_modules/**", "**/node_modules/.vite-temp/**"]
}
}
114 changes: 114 additions & 0 deletions scripts/element-style-transformer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import ts from 'typescript';

export function isElementStyleHelper(node: ts.Statement): boolean {
return !!(
ts.isVariableStatement(node) &&
node.declarationList.flags & ts.NodeFlags.Using &&
node.declarationList.declarations.length === 1 &&
node.declarationList.declarations[0].initializer &&
ts.isCallExpression(node.declarationList.declarations[0].initializer) &&
ts.isPropertyAccessExpression(node.declarationList.declarations[0].initializer.expression) &&
ts.isIdentifier(node.declarationList.declarations[0].initializer.expression.expression) &&
node.declarationList.declarations[0].initializer.expression.expression.text === 'ElementStyleHelper'
);
};


export function elementStyleUsingTransformer() {
return (context: ts.TransformationContext) => {
return (source: ts.SourceFile) => {
// a transformer for a more lightweight "using" declaration. the built-in TS using declarations
// allocate a stack of scopes to register and free stuff. this is way too much overhead for our ElementStyleHelper
// which is called on very low level (e.g. on notes)
// here we convert it to a simple try->finally with some trade-off on variable scopes.
const rewriteElementStyleHelper = (block: ts.Block): ts.Block => {
const newStatements: ts.Statement[] = [];

for (let i = 0; i < block.statements.length; i++) {
const node = block.statements[i];

// using s = ElementStyleHelper.track(...);
// ->
// const s = ElementStyleHelper.track(...);
// try { following statements } finally { s?.[Symbol.Dispose](); }
if (isElementStyleHelper(node)) {
const vs = node as ts.VariableStatement;
// lower using to a simple const
newStatements.push(
ts.factory.createVariableStatement(
vs.modifiers,
ts.factory.createVariableDeclarationList(
[
ts.factory.createVariableDeclaration(
vs.declarationList.declarations[0].name,
undefined,
undefined,
vs.declarationList.declarations[0].initializer
)
],
ts.NodeFlags.Const
)
)
);

// wrap all upcoming statements into a try->finally
// note that this might break variable scopes if not used properly in code
// we do not pull (yet?) any declarations to the outer scope
const tryStatements: ts.Statement[] = [];

i++;
for (; i < block.statements.length; i++) {
if (isElementStyleHelper(block.statements[i])) {
i--;
break;
} else {
tryStatements.push(visitor(block.statements[i]) as ts.Statement);
}
}

// s?.[Symbol.dispose]?.();
const freeResource = ts.factory.createExpressionStatement(
ts.factory.createCallChain(
ts.factory.createElementAccessChain(
ts.factory.createIdentifier(
(vs.declarationList.declarations[0].name as ts.Identifier).text
),
ts.factory.createToken(ts.SyntaxKind.QuestionDotToken),
ts.factory.createPropertyAccessExpression(
ts.factory.createIdentifier('Symbol'),
ts.factory.createIdentifier('dispose')
)
),
ts.factory.createToken(ts.SyntaxKind.QuestionDotToken),
undefined,
undefined
)
);
newStatements.push(
ts.factory.createTryStatement(
ts.factory.createBlock(tryStatements),
undefined,
ts.factory.createBlock([freeResource])
)
);
} else {
newStatements.push(visitor(node) as ts.Statement);
}
}

return ts.factory.createBlock(newStatements, true);
};

const visitor = (node: ts.Node) => {
if (ts.isBlock(node)) {
return rewriteElementStyleHelper(node);
}

return ts.visitEachChild(node, visitor, context);
};

return ts.visitEachChild(source, visitor, context);
};
};
}

5 changes: 5 additions & 0 deletions src.compiler/AstPrinterBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,9 @@ export default abstract class AstPrinterBase {
case cs.SyntaxKind.TryStatement:
this.writeTryStatement(s as cs.TryStatement);
break;
case cs.SyntaxKind.LocalFunction:
this.writeLocalFunction(s as cs.LocalFunctionDeclaration);
break;
}
}

Expand Down Expand Up @@ -597,4 +600,6 @@ export default abstract class AstPrinterBase {
this.writeExpression(expr.value);
this.writeLine(',');
}

protected abstract writeLocalFunction(expr: cs.LocalFunctionDeclaration);
}
136 changes: 6 additions & 130 deletions src.compiler/BuilderHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,78 +54,6 @@ function findNode(node: ts.Node, kind: ts.SyntaxKind): ts.Node | null {
return null;
}

export function getTypeWithNullableInfo(
checker: ts.TypeChecker,
node: ts.TypeNode | ts.Type | undefined,
allowUnionAsPrimitive: boolean
) {
if (!node) {
return {
isNullable: false,
isUnionType: false,
type: {} as ts.Type
};
}

let isNullable = false;
let isUnionType = false;
let type: ts.Type | null = null;
if ('kind' in node) {
if (ts.isUnionTypeNode(node)) {
for (const t of node.types) {
if (t.kind === ts.SyntaxKind.NullKeyword) {
isNullable = true;
} else if (ts.isLiteralTypeNode(t) && t.literal.kind === ts.SyntaxKind.NullKeyword) {
isNullable = true;
} else if (type !== null) {
if (allowUnionAsPrimitive) {
isUnionType = true;
type = checker.getTypeAtLocation(node);
break;
} else {
throw new Error(
'Multi union types on JSON settings not supported: ' +
node.getSourceFile().fileName +
':' +
node.getText()
);
}
} else {
type = checker.getTypeAtLocation(t);
}
}
} else {
type = checker.getTypeAtLocation(node);
}
} else if ('flags' in node) {
if (node.isUnion()) {
for (const t of node.types) {
if ((t.flags & ts.TypeFlags.Null) !== 0 || (t.flags & ts.TypeFlags.Undefined) !== 0) {
isNullable = true;
} else if (type !== null) {
if (allowUnionAsPrimitive) {
isUnionType = true;
type = node;
break;
} else {
throw new Error('Multi union types on JSON settings not supported: ' + node.symbol.name);
}
} else {
type = t;
}
}
} else {
type = node;
}
}

return {
isNullable,
isUnionType,
type: type as ts.Type
};
}

export function unwrapArrayItemType(type: ts.Type, typeChecker: ts.TypeChecker): ts.Type | null {
if (type.symbol && type.symbol.name === 'Array') {
return (type as ts.TypeReference).typeArguments![0];
Expand Down Expand Up @@ -167,7 +95,7 @@ export function isPrimitiveType(type: ts.Type | null) {
return true;
}

return isEnumType(type);
return false;
}

export function isNumberType(type: ts.Type | null) {
Expand All @@ -186,7 +114,7 @@ export function isEnumType(type: ts.Type) {
// if for some reason this returns true...
if (hasFlag(type, ts.TypeFlags.Enum)) return true;
// it's not an enum type if it's an enum literal type
if (hasFlag(type, ts.TypeFlags.EnumLiteral) && !type.isUnion()) return false;
if (hasFlag(type, ts.TypeFlags.EnumLiteral)) return true;
// get the symbol and check if its value declaration is an enum declaration
const symbol = type.getSymbol();
if (!symbol) return false;
Expand All @@ -199,10 +127,6 @@ export function wrapToNonNull(isNullableType: boolean, expr: ts.Expression, fact
return isNullableType ? expr : factory.createNonNullExpression(expr);
}

export function isTypedArray(type: ts.Type) {
return !!type.symbol?.members?.has(ts.escapeLeadingUnderscores('slice'));
}

export function hasFlag(type: ts.Type, flag: ts.TypeFlags): boolean {
return (type.flags & flag) === flag;
}
Expand Down Expand Up @@ -248,9 +172,10 @@ export function cloneTypeNode<T extends ts.Node>(node: T): T {
} else if (ts.isArrayTypeNode(node)) {
return ts.factory.createArrayTypeNode(cloneTypeNode(node.elementType)) as any as T;
} else if (ts.isTypeReferenceNode(node)) {
return ts.factory.createTypeReferenceNode(cloneTypeNode(node.typeName),
node.typeArguments?.map(a => cloneTypeNode(a))
) as any as T;
return ts.factory.createTypeReferenceNode(
cloneTypeNode(node.typeName),
node.typeArguments?.map(a => cloneTypeNode(a))
) as any as T;
} else if (ts.isIdentifier(node)) {
return ts.factory.createIdentifier(node.text) as any as T;
} else if (ts.isQualifiedName(node)) {
Expand All @@ -263,52 +188,3 @@ export function cloneTypeNode<T extends ts.Node>(node: T): T {

throw new Error(`Unsupported TypeNode: '${ts.SyntaxKind[node.kind]}' extend type node cloning`);
}


export function isPrimitiveToJson(type: ts.Type, typeChecker: ts.TypeChecker) {
if (!type) {
return false;
}

const isArray = isTypedArray(type);
const arrayItemType = unwrapArrayItemType(type, typeChecker);

if (hasFlag(type, ts.TypeFlags.Unknown)) {
return true;
}
if (hasFlag(type, ts.TypeFlags.Number)) {
return true;
}
if (hasFlag(type, ts.TypeFlags.String)) {
return true;
}
if (hasFlag(type, ts.TypeFlags.Boolean)) {
return true;
}

if (arrayItemType) {
if (isArray && hasFlag(arrayItemType, ts.TypeFlags.Number)) {
return true;
}
if (isArray && hasFlag(arrayItemType, ts.TypeFlags.String)) {
return true;
}
if (isArray && hasFlag(arrayItemType, ts.TypeFlags.Boolean)) {
return true;
}
} else if (type.symbol) {
switch (type.symbol.name) {
case 'Uint8Array':
case 'Uint16Array':
case 'Uint32Array':
case 'Int8Array':
case 'Int16Array':
case 'Int32Array':
case 'Float32Array':
case 'Float64Array':
return true;
}
}

return false;
}
15 changes: 12 additions & 3 deletions src.compiler/csharp/CSharpAst.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ export enum SyntaxKind {

Attribute,

SpreadExpression
SpreadExpression,
LocalFunction
}

export interface Node {
Expand Down Expand Up @@ -328,6 +329,13 @@ export interface LambdaExpression extends Node {
returnType: TypeNode;
}

export interface LocalFunctionDeclaration extends Node {
name: string;
parameters: ParameterDeclaration[];
body: Block;
returnType: TypeNode;
}

export interface NumericLiteral extends Node {
value: string;
}
Expand Down Expand Up @@ -737,11 +745,12 @@ export function isToDoExpression(node: Node): node is ToDoExpression {
export function isTypeOfExpression(node: Node): node is TypeOfExpression {
return node.nodeType === SyntaxKind.TypeOfExpression;
}

export function isAttribute(node: Node): node is Attribute {
return node.nodeType === SyntaxKind.Attribute;
}

export function isSpreadExpression(node: Node): node is SpreadExpression {
return node.nodeType === SyntaxKind.SpreadExpression;
}
export function isLocalFunction(node: Node): node is LocalFunctionDeclaration {
return node.nodeType === SyntaxKind.LocalFunction;
}
9 changes: 8 additions & 1 deletion src.compiler/csharp/CSharpAstPrinter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -660,7 +660,7 @@ export default class CSharpAstPrinter extends AstPrinterBase {
this.write('TODO: ' + cs.SyntaxKind[type.nodeType]);
break;
}
if ((type.isNullable) && !forNew && !forTypeConstraint) {
if (type.isNullable && !forNew && !forTypeConstraint) {
this.write('?');
}
}
Expand Down Expand Up @@ -967,4 +967,11 @@ export default class CSharpAstPrinter extends AstPrinterBase {
protected writeUsing(using: cs.UsingDeclaration) {
this.writeLine(`using ${using.namespaceOrTypeName};`);
}

protected writeLocalFunction(expr: cs.LocalFunctionDeclaration) {
this.writeType(expr.returnType);
this.write(` ${expr.name}`);
this.writeParameters(expr.parameters);
this.writeBlock(expr.body);
}
}
Loading